From 8f3adbc1ee11b78c84a14285d79823528c2cb07b Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Tue, 2 Dec 2025 21:22:15 -0600 Subject: [PATCH 01/60] Make blocking HTTP client made for virtual threads The client support H1 and H2 using blocking I/O. The biggest differentiator is that the client has a simple blocking-style API made for virtual threads, but still supports full bidirectional h2 streaming. That is, concurrent request/response streaming over H2 using InputStream and OutputStream, allowing you to read response data while still writing request data. Other protocols - ALPN negotiation with automatic protocol selection - H2C prior knowledge (cleartext HTTP/2) Connection Management - Per-route connection pooling with LIFO reuse - HTTP/2 stream multiplexing with load spreading across connections - Idle connection cleanup and validation - Configurable limits (per-route, global, host-specific) Request/Response - h1: Chunked transfer encoding/decoding with trailer support - h1: Expect: 100-continue handling - Content-Length validation - Streaming and buffered exchange APIs to support full bidirectional streaming Proxy - HTTP/HTTPS CONNECT tunneling - Proxy authentication (Basic) - Non-proxy host patterns (wildcards) - Double-TLS (HTTPS proxy to HTTPS target) TLS - TLS hostname verification (certificate CN/SAN matching) - Custom SSLContext support (mTLS, custom CAs) - Separate TLS negotiation timeout Flow Control (H2) - Stream and connection-level window management - Backpressure with configurable write timeout Extensibility - Interceptor chain (request modification, short-circuit, response handling, error recovery) - Connection pool event listener for telemetry, leak detection, etc - Pluggable DNS resolver - Socket factory to customize sockets Timeouts - Connect, TLS, read, write, acquire, and 100-continue timeouts --- .gitignore | 2 + .../smithy-java.java-conventions.gradle.kts | 6 - config/spotbugs/filter.xml | 5 + examples/basic-server/build.gradle.kts | 6 + examples/dynamodb-client/build.gradle.kts | 6 + examples/end-to-end/build.gradle.kts | 6 + .../event-streaming-client/build.gradle.kts | 6 + examples/mcp-traits-example/build.gradle.kts | 6 + examples/restjson-client/build.gradle.kts | 6 + examples/standalone-types/build.gradle.kts | 6 + http/http-client/build.gradle.kts | 152 +++ .../client/VirtualThreadScalingBenchmark.java | 759 ++++++++++++++ .../java/http/client/BenchmarkServer.java | 367 +++++++ .../java/http/client/BoundedInputStream.java | 111 ++ .../http/client/BufferedHttpExchange.java | 76 ++ .../java/http/client/DefaultHttpClient.java | 237 +++++ .../client/DelegatedClosingInputStream.java | 34 + .../client/DelegatedClosingOutputStream.java | 34 + .../smithy/java/http/client/HttpClient.java | 246 +++++ .../smithy/java/http/client/HttpExchange.java | 170 +++ .../java/http/client/HttpInterceptor.java | 205 ++++ .../java/http/client/ManagedHttpExchange.java | 244 +++++ .../http/client/NonClosingOutputStream.java | 54 + .../java/http/client/ProxyConfiguration.java | 240 +++++ .../java/http/client/RequestOptions.java | 188 ++++ .../client/UnsyncBufferedInputStream.java | 249 +++++ .../client/UnsyncBufferedOutputStream.java | 139 +++ .../http/client/connection/CloseReason.java | 54 + .../client/connection/ConnectionPool.java | 68 ++ .../connection/ConnectionPoolListener.java | 75 ++ .../connection/H1ConnectionManager.java | 208 ++++ .../connection/H2ConnectionManager.java | 130 +++ .../client/connection/HttpConnection.java | 90 ++ .../connection/HttpConnectionFactory.java | 254 +++++ .../client/connection/HttpConnectionPool.java | 531 ++++++++++ .../connection/HttpConnectionPoolBuilder.java | 401 +++++++ .../client/connection/HttpSocketFactory.java | 70 ++ .../client/connection/HttpVersionPolicy.java | 47 + .../java/http/client/connection/Route.java | 283 +++++ .../java/http/client/dns/DnsResolver.java | 107 ++ .../http/client/dns/StaticDnsResolver.java | 77 ++ .../http/client/dns/SystemDnsResolver.java | 49 + .../http/client/h1/ChunkedInputStream.java | 288 +++++ .../http/client/h1/ChunkedOutputStream.java | 187 ++++ .../http/client/h1/FailingOutputStream.java | 36 + .../java/http/client/h1/H1Connection.java | 396 +++++++ .../java/http/client/h1/H1Exchange.java | 568 ++++++++++ .../smithy/java/http/client/h1/HttpUtils.java | 133 +++ .../java/http/client/h2/H2Connection.java | 986 ++++++++++++++++++ .../java/http/client/h2/H2Constants.java | 131 +++ .../http/client/h2/H2DataInputStream.java | 86 ++ .../http/client/h2/H2DataOutputStream.java | 76 ++ .../java/http/client/h2/H2Exception.java | 74 ++ .../java/http/client/h2/H2Exchange.java | 785 ++++++++++++++ .../java/http/client/h2/H2FrameCodec.java | 729 +++++++++++++ .../java/http/client/h2/H2StreamWriter.java | 520 +++++++++ .../java/http/client/h2/StreamEvent.java | 73 ++ .../http/client/h2/hpack/DynamicTable.java | 208 ++++ .../http/client/h2/hpack/HpackDecoder.java | 275 +++++ .../http/client/h2/hpack/HpackEncoder.java | 237 +++++ .../java/http/client/h2/hpack/Huffman.java | 769 ++++++++++++++ .../http/client/h2/hpack/StaticTable.java | 211 ++++ .../http/client/Http2IntegrationTest.java | 296 ++++++ .../smithy/java/http/client/HttpBinTest.java | 353 +++++++ settings.gradle.kts | 1 + 65 files changed, 13416 insertions(+), 6 deletions(-) create mode 100644 http/http-client/build.gradle.kts create mode 100644 http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java create mode 100644 http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/BoundedInputStream.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/BufferedHttpExchange.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpClient.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpInterceptor.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/NonClosingOutputStream.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxyConfiguration.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/RequestOptions.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStream.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStream.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/CloseReason.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/ConnectionPool.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/ConnectionPoolListener.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnection.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpVersionPolicy.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/Route.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/DnsResolver.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/StaticDnsResolver.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/SystemDnsResolver.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStream.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/FailingOutputStream.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Connection.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/HttpUtils.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataOutputStream.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exception.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamWriter.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamEvent.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTable.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoder.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/Huffman.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTable.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/Http2IntegrationTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpBinTest.java diff --git a/.gitignore b/.gitignore index 428cf79cd..1ff75ed78 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # Ignore Gradle project-specific cache directory .gradle +.tool-versions + # Ignore kotlin cache dir .kotlin diff --git a/buildSrc/src/main/kotlin/smithy-java.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/smithy-java.java-conventions.gradle.kts index 82d83b6d6..2f12551dc 100644 --- a/buildSrc/src/main/kotlin/smithy-java.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/smithy-java.java-conventions.gradle.kts @@ -13,12 +13,6 @@ plugins { // Workaround per: https://github.com/gradle/gradle/issues/15383 val Project.libs get() = the() -java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } -} - tasks.withType() { options.encoding = "UTF-8" options.release.set(21) diff --git a/config/spotbugs/filter.xml b/config/spotbugs/filter.xml index d7de39b8d..00486470a 100644 --- a/config/spotbugs/filter.xml +++ b/config/spotbugs/filter.xml @@ -86,4 +86,9 @@ + + + + + diff --git a/examples/basic-server/build.gradle.kts b/examples/basic-server/build.gradle.kts index 9d5b6e67a..01e4604ed 100644 --- a/examples/basic-server/build.gradle.kts +++ b/examples/basic-server/build.gradle.kts @@ -4,6 +4,12 @@ plugins { application } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + dependencies { val smithyJavaVersion: String by project diff --git a/examples/dynamodb-client/build.gradle.kts b/examples/dynamodb-client/build.gradle.kts index f5c311e54..194b68ced 100644 --- a/examples/dynamodb-client/build.gradle.kts +++ b/examples/dynamodb-client/build.gradle.kts @@ -4,6 +4,12 @@ plugins { id("me.champeau.jmh") version "0.7.3" } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + dependencies { val smithyJavaVersion: String by project diff --git a/examples/end-to-end/build.gradle.kts b/examples/end-to-end/build.gradle.kts index 6e28437af..706fcd736 100644 --- a/examples/end-to-end/build.gradle.kts +++ b/examples/end-to-end/build.gradle.kts @@ -4,6 +4,12 @@ plugins { application } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + application { mainClass = "software.amazon.smithy.java.server.example.BasicServerExample" } diff --git a/examples/event-streaming-client/build.gradle.kts b/examples/event-streaming-client/build.gradle.kts index 4939995a4..a96f6c06b 100644 --- a/examples/event-streaming-client/build.gradle.kts +++ b/examples/event-streaming-client/build.gradle.kts @@ -4,6 +4,12 @@ plugins { id("me.champeau.jmh") version "0.7.3" } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + dependencies { val smithyJavaVersion: String by project diff --git a/examples/mcp-traits-example/build.gradle.kts b/examples/mcp-traits-example/build.gradle.kts index 86bb0177c..eb02df64d 100644 --- a/examples/mcp-traits-example/build.gradle.kts +++ b/examples/mcp-traits-example/build.gradle.kts @@ -3,6 +3,12 @@ plugins { id("software.amazon.smithy.gradle.smithy-base") } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + dependencies { val smithyJavaVersion: String by project val smithyVersion: String by project diff --git a/examples/restjson-client/build.gradle.kts b/examples/restjson-client/build.gradle.kts index e708045fc..eee9f9e08 100644 --- a/examples/restjson-client/build.gradle.kts +++ b/examples/restjson-client/build.gradle.kts @@ -5,6 +5,12 @@ plugins { id("me.champeau.jmh") version "0.7.3" } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + dependencies { val smithyJavaVersion: String by project diff --git a/examples/standalone-types/build.gradle.kts b/examples/standalone-types/build.gradle.kts index e6f635715..c8d5f831f 100644 --- a/examples/standalone-types/build.gradle.kts +++ b/examples/standalone-types/build.gradle.kts @@ -4,6 +4,12 @@ plugins { id("software.amazon.smithy.gradle.smithy-base") } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + dependencies { val smithyJavaVersion: String by project diff --git a/http/http-client/build.gradle.kts b/http/http-client/build.gradle.kts new file mode 100644 index 000000000..30f000766 --- /dev/null +++ b/http/http-client/build.gradle.kts @@ -0,0 +1,152 @@ +import java.net.Socket +import java.util.Properties + +plugins { + id("smithy-java.module-conventions") + id("me.champeau.jmh") version "0.7.3" +} + +description = "Smithy's generic blocking HTTP client with bidirectional streaming" + +extra["displayName"] = "Smithy :: Java :: HTTP :: Client" +extra["moduleName"] = "software.amazon.smithy.java.http.client" + +// Separate source set for benchmark server (runs in separate process for clean flame graphs) +sourceSets { + create("jmhServer") { + java.srcDir("src/jmhServer/java") + } +} + +val jmhServerImplementation by configurations.getting + +dependencies { + api(project(":http:http-api")) + api(project(":context")) + api(project(":logging")) + + // Netty for HTTP/2 integration tests + testImplementation("io.netty:netty-all:4.1.100.Final") + testImplementation("org.bouncycastle:bcpkix-jdk18on:1.78.1") + + // Add Apache HttpClient for benchmarking comparison + jmh("org.apache.httpcomponents.client5:httpclient5:5.3.1") + + // Helidon WebClient for benchmarking comparison + jmh("io.helidon.webclient:helidon-webclient:4.1.6") + jmh("io.helidon.webclient:helidon-webclient-http2:4.1.6") + + // Netty for raw HTTP/2 benchmarking + jmh("io.netty:netty-all:4.1.100.Final") + + // Benchmark server dependencies (Netty runs in separate process) + jmhServerImplementation("io.netty:netty-all:4.1.100.Final") + jmhServerImplementation("org.bouncycastle:bcpkix-jdk18on:1.78.1") +} + +// Fixed ports for benchmark server (matches BenchmarkServer.java defaults) +val benchmarkH1Port = 18080 +val benchmarkH2Port = 18443 +val benchmarkH2cPort = 18081 + +val benchmarkPidFile = layout.buildDirectory.file("benchmark-server.pid") + +// Capture classpath at configuration time for config cache compatibility +val jmhServerClasspath = sourceSets["jmhServer"].runtimeClasspath + +// Task to start the benchmark server in a background process +val startBenchmarkServer by tasks.registering { + dependsOn("jmhServerClasses") + notCompatibleWithConfigurationCache("Starts external process") + + doLast { + val pidFile = benchmarkPidFile.get().asFile + pidFile.parentFile.mkdirs() + + // Build classpath for server + val serverClasspath = jmhServerClasspath.asPath + + val processBuilder = + ProcessBuilder( + "java", + "-cp", + serverClasspath, + "software.amazon.smithy.java.http.client.BenchmarkServer", + ) + processBuilder.inheritIO() + + val process = processBuilder.start() + + // Store PID for later cleanup + pidFile.writeText(process.pid().toString()) + + // Wait for server to be ready (try connecting) + var attempts = 0 + var ready = false + while (!ready && attempts < 50) { + Thread.sleep(100) + attempts++ + try { + Socket("localhost", benchmarkH2cPort).close() + ready = true + } catch (e: Exception) { + // Server not ready yet + } + } + + if (!ready) { + process.destroyForcibly() + throw GradleException("Benchmark server failed to start (not ready after 5s)") + } + + println("Benchmark server started (PID: ${process.pid()})") + println(" H1: http://localhost:$benchmarkH1Port") + println(" H2: https://localhost:$benchmarkH2Port") + println(" H2C: http://localhost:$benchmarkH2cPort") + } +} + +// Task to stop the benchmark server +val stopBenchmarkServer by tasks.registering { + notCompatibleWithConfigurationCache("Stops external process") + + doLast { + val pidFile = benchmarkPidFile.get().asFile + if (pidFile.exists()) { + val pid = pidFile.readText().trim().toLong() + try { + ProcessHandle.of(pid).ifPresent { handle -> + handle.destroy() + println("Stopped benchmark server (PID: $pid)") + } + } catch (e: Exception) { + println("Warning: Could not stop server: ${e.message}") + } + pidFile.delete() + } + } +} + +// Configure JMH +jmh { + includes = listOf(".*smithyH2c.*") + + warmupIterations = 2 + iterations = 3 + fork = 1 + profilers.add("async:output=flamegraph") + // profilers.add("gc") +} + +// Task that runs JMH with external server (starts server, runs jmh, stops server) +val jmhWithServer by tasks.registering(Exec::class) { + dependsOn(startBenchmarkServer) + finalizedBy(stopBenchmarkServer) + notCompatibleWithConfigurationCache("Runs JMH with external server") + + workingDir = project.rootDir + commandLine( + "./gradlew", + ":http:http-client:jmh", + ) +} diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java new file mode 100644 index 000000000..24dfa5e00 --- /dev/null +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java @@ -0,0 +1,759 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import io.helidon.common.tls.Tls; +import io.helidon.webclient.api.HttpClientResponse; +import io.helidon.webclient.api.WebClient; +import io.helidon.webclient.http2.Http2Client; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.Http2FrameCodecBuilder; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import io.netty.handler.codec.http2.Http2MultiplexHandler; +import io.netty.handler.codec.http2.Http2StreamChannel; +import io.netty.handler.codec.http2.Http2StreamChannelBootstrap; +import io.netty.handler.codec.http2.Http2StreamFrame; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URI; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.util.Timeout; +import org.openjdk.jmh.annotations.AuxCounters; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.dns.DnsResolver; + +/** + * Benchmark comparing HTTP/1.1 vs HTTP/2 clients at extreme concurrency. + * + *

Uses an external Netty server (separate process) to get clean flame graphs + * without server code polluting the profile. + * + *

Run with external server (recommended for profiling): + *

+ * ./gradlew :http:http-client:jmhWithServer
+ * 
+ * + *

Or manually: + *

+ * # Terminal 1: Start server
+ * ./gradlew :http:http-client:startBenchmarkServer
+ *
+ * # Terminal 2: Run benchmark
+ * ./gradlew :http:http-client:jmh --args='-p externalServer=http://localhost:PORT'
+ *
+ * # Terminal 1: Stop server when done
+ * ./gradlew :http:http-client:stopBenchmarkServer
+ * 
+ */ +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +@Warmup(iterations = 2, time = 5) +@Measurement(iterations = 3, time = 5) +@Fork(value = 1, jvmArgs = {"-Xms2g", "-Xmx2g"}) +@State(Scope.Benchmark) +public class VirtualThreadScalingBenchmark { + + @Param({"5000"}) + private int concurrency; + + // Server URL - use jmhWithServer task or set manually + // For h2c: "http://localhost:18081" (default) + // For h2 TLS: "https://localhost:18443" + // For h1: "http://localhost:18080" + @Param({"http://localhost:18081"}) + private String externalServer; + + // HTTP/1.1 clients + private HttpClient smithyClientH1; + private CloseableHttpClient apacheClientH1; + private WebClient helidonClientH1; + + // HTTP/2 cleartext clients (h2c) + private HttpClient smithyClientH2c; + private Http2Client helidonClientH2c; + + // HTTP/2 TLS clients (h2) + private HttpClient smithyClientH2; + private Http2Client helidonClientH2; + + // Netty HTTP/2 client (raw, no abstractions) + // Note: Netty performs better with 1 connection (606K) vs multiple (425K with 100 connections) + // This is because Netty's event-loop model multiplexes efficiently on a single connection + private EventLoopGroup nettyEventLoopGroup; + private Channel nettyH2cChannel; + + // Server URLs + private String h1BaseUrl; + private String h2BaseUrl; + private String h2cBaseUrl; + + // SSL context for clients + private SSLContext trustAllSslContext; + + @Setup + public void setup() throws Exception { + if (externalServer == null || externalServer.isEmpty()) { + throw new IllegalStateException( + "externalServer parameter is required. Use ./gradlew :http:http-client:jmhWithServer " + + "or set -p externalServer=http://localhost:PORT"); + } + + // Fixed server ports (must match BenchmarkServer.java) + // externalServer is used as the base host, ports are fixed + String host = externalServer.replaceAll("https?://", "").replaceAll(":\\d+.*", ""); + h1BaseUrl = "http://" + host + ":18080"; + h2BaseUrl = "https://" + host + ":18443"; + h2cBaseUrl = "http://" + host + ":18081"; + + System.out.println("Using external server: " + externalServer); + + // Create trust-all SSL context for self-signed cert + trustAllSslContext = createTrustAllSslContext(); + + // Static DNS resolver to bypass DNS overhead + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + + // ===== HTTP/1.1 Clients ===== + + // Smithy H1 client + smithyClientH1 = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(5000) + .maxTotalConnections(5000) + .maxIdleTime(Duration.ofMinutes(2)) + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .dnsResolver(staticDns) + .build()) + .build(); + + // Apache H1 client (Apache HttpClient 5 classic API only supports H1) + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); + connectionManager.setMaxTotal(5000); + connectionManager.setDefaultMaxPerRoute(5000); + connectionManager.setDefaultConnectionConfig(ConnectionConfig.custom() + .setConnectTimeout(Timeout.ofSeconds(10)) + .setSocketTimeout(Timeout.ofSeconds(30)) + .build()); + + apacheClientH1 = HttpClients.custom() + .setConnectionManager(connectionManager) + .setDefaultRequestConfig(RequestConfig.custom() + .setConnectionRequestTimeout(Timeout.ofSeconds(5)) + .build()) + .build(); + + // Helidon H1 client + helidonClientH1 = WebClient.builder() + .baseUri(h1BaseUrl) + .shareConnectionCache(false) + .connectionCacheSize(5000) + .build(); + + // ===== HTTP/2 Cleartext Clients (h2c) ===== + + // Smithy H2c client (cleartext, prior knowledge) + smithyClientH2c = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(5000) + .maxTotalConnections(5000) + .maxIdleTime(Duration.ofMinutes(2)) + .dnsResolver(staticDns) + .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) + .build()) + .build(); + + // Helidon H2c client (cleartext, prior knowledge) + helidonClientH2c = Http2Client.builder() + .baseUri(h2cBaseUrl) + .shareConnectionCache(false) + .protocolConfig(pc -> pc.priorKnowledge(true)) + .build(); + + // ===== HTTP/2 TLS Clients (h2 with ALPN) ===== + + // Smithy H2 client (TLS with ALPN) + smithyClientH2 = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(5000) + .maxTotalConnections(5000) + .maxIdleTime(Duration.ofMinutes(2)) + .dnsResolver(staticDns) + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_2) + .sslContext(trustAllSslContext) + .build()) + .build(); + + // Helidon H2 client (TLS with ALPN) + // priorKnowledge(false) = use ALPN for protocol negotiation over TLS + // shareConnectionCache(true) is essential for HTTP/2 multiplexing + helidonClientH2 = Http2Client.builder() + .baseUri(h2BaseUrl) + .shareConnectionCache(true) + .tls(Tls.builder().trustAll(true).build()) + .protocolConfig(pc -> pc.priorKnowledge(false)) + .build(); + + // ===== Netty HTTP/2 Client (raw, no abstractions) ===== + nettyEventLoopGroup = new NioEventLoopGroup(); + Bootstrap nettyBootstrap = new Bootstrap(); + nettyBootstrap.group(nettyEventLoopGroup) + .channel(NioSocketChannel.class) + .option(ChannelOption.SO_KEEPALIVE, true) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ch.pipeline() + .addLast( + Http2FrameCodecBuilder.forClient() + .initialSettings( + io.netty.handler.codec.http2.Http2Settings.defaultSettings() + .maxConcurrentStreams(10000)) + .build(), + new Http2MultiplexHandler(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0( + ChannelHandlerContext ctx, + Http2StreamFrame msg + ) { + // Inbound stream handler (for server push, not used here) + } + })); + } + }); + + // Connect to h2c server (single connection - Netty performs better this way) + String h2cHost = h2cBaseUrl.replaceAll("https?://", "").replaceAll(":\\d+.*", ""); + int h2cPort = 18081; + nettyH2cChannel = nettyBootstrap.connect(new InetSocketAddress(h2cHost, h2cPort)).sync().channel(); + } + + private SSLContext createTrustAllSslContext() throws Exception { + TrustManager[] trustAllCerts = new TrustManager[] { + new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void checkClientTrusted(X509Certificate[] certs, String authType) {} + + public void checkServerTrusted(X509Certificate[] certs, String authType) {} + } + }; + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, new SecureRandom()); + return sslContext; + } + + @TearDown + public void teardown() throws Exception { + // Close H1 clients + if (smithyClientH1 != null) { + smithyClientH1.close(); + } + if (apacheClientH1 != null) { + apacheClientH1.close(); + } + if (helidonClientH1 != null) { + helidonClientH1.closeResource(); + } + + // Close H2c clients + if (smithyClientH2c != null) { + smithyClientH2c.close(); + } + if (helidonClientH2c != null) { + helidonClientH2c.closeResource(); + } + + // Close H2 TLS clients + if (smithyClientH2 != null) { + smithyClientH2.close(); + } + if (helidonClientH2 != null) { + helidonClientH2.closeResource(); + } + + // Close Netty client + if (nettyH2cChannel != null) { + nettyH2cChannel.close().sync(); + } + if (nettyEventLoopGroup != null) { + nettyEventLoopGroup.shutdownGracefully().sync(); + } + } + + @AuxCounters(AuxCounters.Type.EVENTS) + @State(Scope.Thread) + public static class RequestCounter { + public long requests; + public long errors; + + @Setup(Level.Iteration) + public void reset() { + requests = 0; + errors = 0; + } + } + + // ===== HTTP/1.1 Benchmarks ===== + + /** + * Smithy HTTP/1.1 client throughput with virtual threads. + */ + @Benchmark + @Threads(1) + public void smithyH1(RequestCounter counter) throws InterruptedException { + var requests = new AtomicLong(); + var errors = new AtomicLong(); + AtomicReference firstError = new AtomicReference<>(); + var running = new AtomicBoolean(true); + var latch = new CountDownLatch(concurrency); + var uri = URI.create(h1BaseUrl + "/get"); + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < concurrency; i++) { + executor.submit(() -> { + try { + while (running.get()) { + // Close will drain the remaining bytes + smithyClientH1.send(HttpRequest.builder().uri(uri).method("GET").build()).close(); + requests.incrementAndGet(); + } + } catch (IOException e) { + errors.incrementAndGet(); + firstError.compareAndSet(null, e); + } finally { + latch.countDown(); + } + }); + } + + Thread.sleep(1000); + running.set(false); + var _ignored = latch.await(5, TimeUnit.SECONDS); + } + + counter.requests = requests.get(); + counter.errors = errors.get(); + + // Log first error for debugging + if (firstError.get() != null) { + System.err.println("errors: " + errors.get() + ", first error:"); + firstError.get().printStackTrace(System.err); + } + } + + /** + * Apache HTTP/1.1 client throughput with virtual threads. + * Note: Apache HttpClient 5 classic API only supports HTTP/1.1. + */ + @Benchmark + @Threads(1) + public void apacheH1(RequestCounter counter) throws InterruptedException { + var requests = new AtomicLong(); + var errors = new AtomicLong(); + var running = new AtomicBoolean(true); + var latch = new CountDownLatch(concurrency); + var target = h1BaseUrl + "/get"; + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < concurrency; i++) { + executor.submit(() -> { + try { + while (running.get()) { + HttpGet request = new HttpGet(target); + try (CloseableHttpResponse response = apacheClientH1.execute(request)) { + EntityUtils.consume(response.getEntity()); + } + requests.incrementAndGet(); + } + } catch (Exception ignored) { + errors.incrementAndGet(); + } finally { + latch.countDown(); + } + }); + } + + Thread.sleep(1000); + running.set(false); + var _ignored = latch.await(5, TimeUnit.SECONDS); + } + + counter.requests = requests.get(); + counter.errors = errors.get(); + } + + /** + * Helidon HTTP/1.1 client throughput with virtual threads. + */ + @Benchmark + @Threads(1) + public void helidonH1(RequestCounter counter) throws InterruptedException { + var requests = new AtomicLong(); + var errors = new AtomicLong(); + var running = new AtomicBoolean(true); + var latch = new CountDownLatch(concurrency); + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < concurrency; i++) { + executor.submit(() -> { + try { + while (running.get()) { + try (HttpClientResponse response = helidonClientH1.get("/get").request()) { + response.entity().consume(); + } + requests.incrementAndGet(); + } + } catch (Exception e) { + errors.incrementAndGet(); + } finally { + latch.countDown(); + } + }); + } + + Thread.sleep(1000); + running.set(false); + var _ignored = latch.await(5, TimeUnit.SECONDS); + } + + counter.requests = requests.get(); + counter.errors = errors.get(); + } + + // ===== HTTP/2c Benchmarks ===== + + @Benchmark + @Threads(1) + public void smithyH2c(RequestCounter counter) throws InterruptedException { + var requests = new AtomicLong(); + var errors = new AtomicLong(); + AtomicReference firstError = new AtomicReference<>(); + var running = new AtomicBoolean(true); + var latch = new CountDownLatch(concurrency); + var uri = URI.create(h2cBaseUrl + "/get"); + var request = HttpRequest.builder().uri(uri).method("GET").build(); + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < concurrency; i++) { + executor.submit(() -> { + try { + while (running.get()) { + try (var res = smithyClientH2c.send(request)) { + res.body().asInputStream().readAllBytes(); + requests.incrementAndGet(); + } + } + } catch (Exception e) { + errors.incrementAndGet(); + firstError.compareAndSet(null, e); + } finally { + latch.countDown(); + } + }); + } + + Thread.sleep(1000); + running.set(false); + var _ignored = latch.await(5, TimeUnit.SECONDS); + } + + counter.requests = requests.get(); + counter.errors = errors.get(); + + // Log first error for debugging + if (firstError.get() != null) { + System.err.println("errors: " + errors.get() + ", first error:"); + firstError.get().printStackTrace(System.err); + } + } + + @Benchmark + @Threads(1) + public void helidonH2c(RequestCounter counter) throws InterruptedException { + var requests = new AtomicLong(); + var errors = new AtomicLong(); + var firstError = new AtomicReference(); + var running = new AtomicBoolean(true); + var latch = new CountDownLatch(concurrency); + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < concurrency; i++) { + executor.submit(() -> { + try { + while (running.get()) { + try (HttpClientResponse response = helidonClientH2c.get("/get").request()) { + response.entity().consume(); + } + requests.incrementAndGet(); + } + } catch (Exception e) { + errors.incrementAndGet(); + firstError.compareAndSet(null, e); + } finally { + latch.countDown(); + } + }); + } + + Thread.sleep(1000); + running.set(false); + var _ignored = latch.await(5, TimeUnit.SECONDS); + } + + counter.requests = requests.get(); + counter.errors = errors.get(); + + // Log first error for debugging + if (firstError.get() != null) { + System.err.println("Helidon H2c errors: " + errors.get() + ", first error:"); + firstError.get().printStackTrace(System.err); + } + } + + /** + * Raw Netty HTTP/2 client throughput. + * + *

Uses Netty's HTTP/2 multiplexing with a single connection and event-loop threads. + * Netty performs better with 1 connection (606K) vs multiple (425K with 100 connections) + * because its event-loop model multiplexes efficiently on a single connection. + */ + @Benchmark + @Threads(1) + public void nettyH2c(RequestCounter counter) throws InterruptedException { + var requests = new AtomicLong(); + var errors = new AtomicLong(); + var firstError = new AtomicReference(); + var running = new AtomicBoolean(true); + var activeTasks = new AtomicLong(concurrency); + + // Create stream bootstrap for multiplexing on single connection + Http2StreamChannelBootstrap streamBootstrap = new Http2StreamChannelBootstrap(nettyH2cChannel); + + // Handler that opens a new stream, sends request, and repeats on response + Runnable[] makeRequest = new Runnable[1]; + makeRequest[0] = () -> { + if (!running.get()) { + activeTasks.decrementAndGet(); + return; + } + + streamBootstrap.open().addListener(future -> { + if (!future.isSuccess()) { + errors.incrementAndGet(); + firstError.compareAndSet(null, future.cause()); + if (!running.get()) { + activeTasks.decrementAndGet(); + } else { + // Retry + makeRequest[0].run(); + } + return; + } + + Http2StreamChannel streamChannel = (Http2StreamChannel) future.get(); + streamChannel.pipeline().addLast(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame frame) { + boolean endStream = false; + if (frame instanceof Http2HeadersFrame headersFrame) { + endStream = headersFrame.isEndStream(); + } else if (frame instanceof io.netty.handler.codec.http2.Http2DataFrame dataFrame) { + endStream = dataFrame.isEndStream(); + } + + if (endStream) { + requests.incrementAndGet(); + ctx.close(); + // Start next request on new stream + makeRequest[0].run(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + errors.incrementAndGet(); + firstError.compareAndSet(null, cause); + ctx.close(); + // Retry on new stream + makeRequest[0].run(); + } + }); + + // Send request immediately after adding handler + DefaultHttp2Headers headers = new DefaultHttp2Headers(); + headers.method("GET"); + headers.path("/get"); + headers.scheme("http"); + headers.authority("localhost:18081"); + streamChannel.writeAndFlush(new io.netty.handler.codec.http2.DefaultHttp2HeadersFrame(headers, true)); + }); + }; + + // Start concurrent request loops + for (int i = 0; i < concurrency; i++) { + makeRequest[0].run(); + } + + // Run for 1 second like other benchmarks + Thread.sleep(1000); + running.set(false); + + // Wait for active tasks to complete + long deadline = System.currentTimeMillis() + 5000; + while (activeTasks.get() > 0 && System.currentTimeMillis() < deadline) { + Thread.sleep(10); + } + + counter.requests = requests.get(); + counter.errors = errors.get(); + + // Log first error for debugging + if (firstError.get() != null) { + System.err.println("Netty H2c errors: " + errors.get() + ", first error:"); + firstError.get().printStackTrace(System.err); + } + } + + // ===== HTTP/2 TLS Benchmarks ===== + + @Benchmark + @Threads(1) + public void smithyH2(RequestCounter counter) throws InterruptedException { + var requests = new AtomicLong(); + var errors = new AtomicLong(); + var firstError = new AtomicReference(); + var running = new AtomicBoolean(true); + var latch = new CountDownLatch(concurrency); + var uri = URI.create(h2BaseUrl + "/get"); + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < concurrency; i++) { + executor.submit(() -> { + try { + while (running.get()) { + smithyClientH2.send(HttpRequest.builder().uri(uri).method("GET").build()); + requests.incrementAndGet(); + } + } catch (Exception e) { + errors.incrementAndGet(); + firstError.compareAndSet(null, e); + } finally { + latch.countDown(); + } + }); + } + + Thread.sleep(1000); + running.set(false); + var _ignored = latch.await(5, TimeUnit.SECONDS); + } + + counter.requests = requests.get(); + counter.errors = errors.get(); + + // Log first error for debugging + if (firstError.get() != null) { + System.err.println("H2 TLS errors: " + errors.get() + ", first error:"); + firstError.get().printStackTrace(System.err); + } + } + + @Benchmark + @Threads(1) + public void helidonH2(RequestCounter counter) throws InterruptedException { + var requests = new AtomicLong(); + var errors = new AtomicLong(); + var firstError = new AtomicReference(); + var running = new AtomicBoolean(true); + var latch = new CountDownLatch(concurrency); + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < concurrency; i++) { + executor.submit(() -> { + try { + while (running.get()) { + try (HttpClientResponse response = helidonClientH2.get("/get").request()) { + response.entity().consume(); + } + requests.incrementAndGet(); + } + } catch (Exception e) { + errors.incrementAndGet(); + firstError.compareAndSet(null, e); + } finally { + latch.countDown(); + } + }); + } + + Thread.sleep(1000); + running.set(false); + var _ignored = latch.await(5, TimeUnit.SECONDS); + } + + counter.requests = requests.get(); + counter.errors = errors.get(); + + // Log first error for debugging + if (firstError.get() != null) { + System.err.println("Helidon H2 TLS errors: " + errors.get() + ", first error:"); + firstError.get().printStackTrace(System.err); + } + } +} diff --git a/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java new file mode 100644 index 000000000..25544dfbb --- /dev/null +++ b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java @@ -0,0 +1,367 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.netty.handler.codec.http.HttpHeaderNames.KEEP_ALIVE; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; +import io.netty.handler.codec.http2.Http2DataFrame; +import io.netty.handler.codec.http2.Http2FrameCodecBuilder; +import io.netty.handler.codec.http2.Http2FrameStream; +import io.netty.handler.codec.http2.Http2Headers; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import io.netty.handler.codec.http2.Http2SecurityUtil; +import io.netty.handler.ssl.ApplicationProtocolConfig; +import io.netty.handler.ssl.ApplicationProtocolNames; +import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SupportedCipherSuiteFilter; +import io.netty.handler.ssl.util.SelfSignedCertificate; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; + +/** + * Standalone Netty-based benchmark server. + * + *

Runs in a separate process from JMH benchmarks to get clean flame graphs + * without Netty code polluting the profile. + * + *

Writes port information to a file for the benchmark to read: + *

+ * h1Port=12345
+ * h2Port=12346
+ * h2cPort=12347
+ * 
+ * + *

Usage: + *

+ * java -cp ... software.amazon.smithy.java.http.client.BenchmarkServer [port-file]
+ * 
+ */ +public final class BenchmarkServer { + + private static final byte[] CONTENT = "{\"status\":\"ok\"}".getBytes(StandardCharsets.UTF_8); + + // Fixed ports for benchmark server (avoids dynamic port discovery complexity) + public static final int DEFAULT_H1_PORT = 18080; + public static final int DEFAULT_H2_PORT = 18443; + public static final int DEFAULT_H2C_PORT = 18081; + + private final EventLoopGroup bossGroup; + private final EventLoopGroup workerGroup; + private final Channel h1ServerChannel; + private final Channel h2ServerChannel; + private final Channel h2cServerChannel; + private final int h1Port; + private final int h2Port; + private final int h2cPort; + + public BenchmarkServer() throws Exception { + this(DEFAULT_H1_PORT, DEFAULT_H2_PORT, DEFAULT_H2C_PORT); + } + + public BenchmarkServer(int h1Port, int h2Port, int h2cPort) throws Exception { + this.h1Port = h1Port; + this.h2Port = h2Port; + this.h2cPort = h2cPort; + + bossGroup = new NioEventLoopGroup(1); + workerGroup = new NioEventLoopGroup(); + + // Start HTTP/1.1 server + h1ServerChannel = startH1Server(h1Port); + + // Start HTTP/2 server with TLS (h2) + h2ServerChannel = startH2Server(h2Port); + + // Start HTTP/2 cleartext server (h2c) - prior knowledge only + h2cServerChannel = startH2cServer(h2cPort); + } + + public int getH1Port() { + return h1Port; + } + + public int getH2Port() { + return h2Port; + } + + public int getH2cPort() { + return h2cPort; + } + + private Channel startH1Server(int port) throws InterruptedException { + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .option(ChannelOption.SO_BACKLOG, 16384) + .option(ChannelOption.SO_REUSEADDR, true) + .childOption(ChannelOption.SO_KEEPALIVE, true) + .childOption(ChannelOption.TCP_NODELAY, true) + .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + ch.pipeline() + .addLast(new HttpServerCodec()) + .addLast(new HttpObjectAggregator(8192)) + .addLast(new Http1RequestHandler()); + } + }); + + return b.bind(port).sync().channel(); + } + + private Channel startH2Server(int port) throws Exception { + // Create self-signed certificate (uses BouncyCastle) + SelfSignedCertificate ssc = new SelfSignedCertificate(); + + // Build SSL context with ALPN for HTTP/2 + SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) + .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE) + .applicationProtocolConfig(new ApplicationProtocolConfig( + ApplicationProtocolConfig.Protocol.ALPN, + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + ApplicationProtocolNames.HTTP_2, + ApplicationProtocolNames.HTTP_1_1)) + .build(); + + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .option(ChannelOption.SO_BACKLOG, 16384) + .option(ChannelOption.SO_REUSEADDR, true) + .childOption(ChannelOption.SO_KEEPALIVE, true) + .childOption(ChannelOption.TCP_NODELAY, true) + .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + ch.pipeline().addLast(sslCtx.newHandler(ch.alloc())); + ch.pipeline().addLast(new Http2OrHttpHandler()); + } + }); + + return b.bind(port).sync().channel(); + } + + /** + * Start HTTP/2 cleartext server (h2c) with prior knowledge. + * No TLS, no upgrade - client must speak HTTP/2 directly. + */ + private Channel startH2cServer(int port) throws InterruptedException { + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .option(ChannelOption.SO_BACKLOG, 16384) + .option(ChannelOption.SO_REUSEADDR, true) + .childOption(ChannelOption.SO_KEEPALIVE, true) + .childOption(ChannelOption.TCP_NODELAY, true) + .childOption(ChannelOption.SO_RCVBUF, 1048576) + .childOption(ChannelOption.SO_SNDBUF, 1048576) + .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + // h2c prior knowledge: HTTP/2 directly without TLS or upgrade + ch.pipeline() + .addLast( + Http2FrameCodecBuilder.forServer() + .initialSettings( + io.netty.handler.codec.http2.Http2Settings.defaultSettings() + .maxConcurrentStreams(10000) + .initialWindowSize(1048576)) + .build(), + Http2RequestHandler.INSTANCE); + } + }); + + return b.bind(port).sync().channel(); + } + + public void shutdown() throws InterruptedException { + if (h1ServerChannel != null) { + h1ServerChannel.close().sync(); + } + if (h2ServerChannel != null) { + h2ServerChannel.close().sync(); + } + if (h2cServerChannel != null) { + h2cServerChannel.close().sync(); + } + if (bossGroup != null) { + bossGroup.shutdownGracefully().sync(); + } + if (workerGroup != null) { + workerGroup.shutdownGracefully().sync(); + } + } + + /** + * Handler for HTTP/1.1 requests. + */ + private static class Http1RequestHandler extends SimpleChannelInboundHandler { + @Override + protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) { + FullHttpResponse response = new DefaultFullHttpResponse( + HTTP_1_1, + OK, + Unpooled.wrappedBuffer(CONTENT)); + response.headers() + .set(CONTENT_TYPE, "application/json") + .set(CONNECTION, KEEP_ALIVE) + .setInt(CONTENT_LENGTH, CONTENT.length); + ctx.writeAndFlush(response); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + ctx.close(); + } + } + + /** + * ALPN handler that configures the pipeline for HTTP/2 or HTTP/1.1. + */ + private static class Http2OrHttpHandler extends ApplicationProtocolNegotiationHandler { + Http2OrHttpHandler() { + super(ApplicationProtocolNames.HTTP_1_1); + } + + @Override + protected void configurePipeline(ChannelHandlerContext ctx, String protocol) { + if (ApplicationProtocolNames.HTTP_2.equals(protocol)) { + ctx.pipeline() + .addLast( + Http2FrameCodecBuilder.forServer() + .initialSettings(io.netty.handler.codec.http2.Http2Settings.defaultSettings() + .maxConcurrentStreams(10000) + .initialWindowSize(1048576)) + .build(), + Http2RequestHandler.INSTANCE); + } else { + ctx.pipeline() + .addLast( + new HttpServerCodec(), + new HttpObjectAggregator(8192), + new Http1RequestHandler()); + } + } + } + + /** + * Handler for HTTP/2 requests using the frame codec API. + */ + @io.netty.channel.ChannelHandler.Sharable + private static class Http2RequestHandler extends ChannelInboundHandlerAdapter { + static final Http2RequestHandler INSTANCE = new Http2RequestHandler(); + private static final Http2Headers RESPONSE_HEADERS = new DefaultHttp2Headers(true, 3) + .status("200") + .set("content-type", "application/json") + .setInt("content-length", CONTENT.length); + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + if (msg instanceof Http2HeadersFrame headersFrame) { + if (headersFrame.isEndStream()) { + sendResponse(ctx, headersFrame.stream()); + } + } else if (msg instanceof Http2DataFrame dataFrame) { + dataFrame.release(); + if (dataFrame.isEndStream()) { + sendResponse(ctx, dataFrame.stream()); + } + } + } + + private void sendResponse(ChannelHandlerContext ctx, Http2FrameStream stream) { + ctx.write(new DefaultHttp2HeadersFrame(RESPONSE_HEADERS, false).stream(stream)); + ctx.writeAndFlush(new DefaultHttp2DataFrame( + Unpooled.wrappedBuffer(CONTENT), + true).stream(stream)); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + ctx.close(); + } + } + + /** + * Write port configuration to a file for the benchmark to read. + */ + public void writePortFile(File portFile) throws IOException { + try (FileWriter writer = new FileWriter(portFile, StandardCharsets.UTF_8)) { + writer.write("h1Port=" + h1Port + "\n"); + writer.write("h2Port=" + h2Port + "\n"); + writer.write("h2cPort=" + h2cPort + "\n"); + } + } + + public static void main(String[] args) throws Exception { + // Default port file location + String portFilePath = args.length > 0 ? args[0] : "build/benchmark-server-ports.properties"; + + System.out.println("Starting benchmark server..."); + BenchmarkServer server = new BenchmarkServer(); + + System.out.println("HTTP/1.1 server: http://localhost:" + server.getH1Port()); + System.out.println("HTTP/2 (TLS) server: https://localhost:" + server.getH2Port()); + System.out.println("HTTP/2 (h2c) server: http://localhost:" + server.getH2cPort()); + + // Write port file + File portFile = new File(portFilePath); + portFile.getParentFile().mkdirs(); + server.writePortFile(portFile); + System.out.println("Port file written to: " + portFile.getAbsolutePath()); + + // Wait for shutdown signal + System.out.println("Press Ctrl+C to stop..."); + CountDownLatch shutdownLatch = new CountDownLatch(1); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.out.println("\nShutting down..."); + try { + server.shutdown(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + shutdownLatch.countDown(); + })); + + shutdownLatch.await(); + System.out.println("Server stopped."); + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/BoundedInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/BoundedInputStream.java new file mode 100644 index 000000000..060a15287 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/BoundedInputStream.java @@ -0,0 +1,111 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.io.IOException; +import java.io.InputStream; + +/** + * InputStream that reads exactly a specified number of bytes. + * + *

Used for HTTP responses with Content-Length. Note that this does not close the delegate InputStream on close. + */ +public final class BoundedInputStream extends InputStream { + private final InputStream delegate; + private long remaining; + private boolean closed; + + public BoundedInputStream(InputStream delegate, long length) { + this.delegate = delegate; + this.remaining = length; + } + + @Override + public int read() throws IOException { + if (closed || remaining <= 0) { + return -1; + } + + int b = delegate.read(); + if (b != -1) { + remaining--; + } else if (remaining > 0) { + // Server closed connection before sending all Content-Length bytes + throw new IOException("Premature EOF: expected " + remaining + + " more bytes based on Content-Length"); + } + return b; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (closed || remaining <= 0) { + return -1; + } + + int toRead = (int) Math.min(len, remaining); + int n = delegate.read(b, off, toRead); + + if (n > 0) { + remaining -= n; + } else if (n == -1 && remaining > 0) { + // Server closed connection before sending all Content-Length bytes + throw new IOException("Premature EOF: expected " + remaining + + " more bytes based on Content-Length"); + } + + return n; + } + + @Override + public long skip(long n) throws IOException { + if (closed || remaining <= 0) { + return 0; + } + + long toSkip = Math.min(n, remaining); + long skipped = delegate.skip(toSkip); + + if (skipped > 0) { + remaining -= skipped; + } + + return skipped; + } + + @Override + public int available() throws IOException { + if (closed || remaining <= 0) { + return 0; + } + + int available = delegate.available(); + return (int) Math.min(available, remaining); + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + closed = true; + + // Drain remaining bytes so connection can be reused + if (remaining > 0) { + byte[] drain = new byte[(int) Math.min(8192, remaining)]; + while (remaining > 0) { + int toRead = (int) Math.min(drain.length, remaining); + int n = delegate.read(drain, 0, toRead); + if (n == -1) { + throw new IOException("Premature EOF while draining response body: expected " + + remaining + " more bytes based on Content-Length"); + } + remaining -= n; + } + } + // Don't close delegate - connection may be reused + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/BufferedHttpExchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/BufferedHttpExchange.java new file mode 100644 index 000000000..8c36a38fb --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/BufferedHttpExchange.java @@ -0,0 +1,76 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.api.HttpVersion; + +/** + * HttpExchange implementation backed by a buffered response. + * + *

Used when an interceptor short-circuits the request via {@code handleRequest()} + * or {@code onError()}, returning a pre-existing response (e.g., from cache). + * + *

The request body is a no-op since the request was never actually sent. + */ +final class BufferedHttpExchange implements HttpExchange { + private final HttpRequest request; + private final HttpResponse response; + private final OutputStream noopRequestBody = OutputStream.nullOutputStream(); + + BufferedHttpExchange(HttpRequest request, HttpResponse response) { + this.request = request; + this.response = response; + } + + @Override + public HttpRequest request() { + return request; + } + + @Override + public OutputStream requestBody() { + // No-op - request was never sent (short-circuited) + return noopRequestBody; + } + + @Override + public InputStream responseBody() { + return response.body().asInputStream(); + } + + @Override + public HttpHeaders responseHeaders() { + return response.headers(); + } + + @Override + public int responseStatusCode() { + return response.statusCode(); + } + + @Override + public HttpVersion responseVersion() throws IOException { + return response.httpVersion(); + } + + @Override + public void close() throws IOException { + // Nothing to close - no real connection + // Response body will be closed when user closes it + } + + @Override + public boolean supportsBidirectionalStreaming() { + // Buffered response - no real connection, no streaming + return false; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java new file mode 100644 index 000000000..c0541d9f9 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java @@ -0,0 +1,237 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.io.IOException; +import java.io.OutputStream; +import java.time.Duration; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import software.amazon.smithy.java.context.Context; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.client.connection.ConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpConnection; +import software.amazon.smithy.java.http.client.connection.Route; +import software.amazon.smithy.java.io.datastream.DataStream; + +/** + * Default {@link HttpClient} implementation. + */ +final class DefaultHttpClient implements HttpClient { + + private final ConnectionPool connectionPool; + private final ProxyConfiguration proxyConfiguration; + private final List interceptors; + private final Duration requestTimeout; + private final ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor(); + + DefaultHttpClient(Builder builder) { + this.connectionPool = builder.connectionPool; + this.interceptors = List.copyOf(builder.interceptors); + this.proxyConfiguration = builder.proxyConfiguration; + this.requestTimeout = builder.requestTimeout; + } + + @Override + public HttpResponse send(HttpRequest request, RequestOptions options) throws IOException { + Duration timeout = options.requestTimeout() != null ? options.requestTimeout() : requestTimeout; + return timeout != null ? sendWithTimeout(request, options, timeout) : sendInternal(request, options); + } + + private HttpResponse sendInternal(HttpRequest request, RequestOptions options) throws IOException { + // exchange() handles beforeRequest, preemptRequest, onError, and creates ManagedHttpExchange + // which applies interceptResponse lazily when response is accessed. + HttpExchange exchange = newExchange(request, options); + + // Write request body using the effective request + HttpRequest effectiveRequest = exchange.request(); + try { + DataStream requestBody = effectiveRequest.body(); + if (requestBody != null && requestBody.contentLength() != 0) { + try (OutputStream out = exchange.requestBody()) { + requestBody.asInputStream().transferTo(out); + } + } else { + exchange.requestBody().close(); + } + } catch (IOException e) { + exchange.close(); + throw e; + } + + // Build streaming response. The response body stream auto-closes the exchange when closed. + return HttpResponse.builder() + .statusCode(exchange.responseStatusCode()) + .headers(exchange.responseHeaders()) + .body(DataStream.ofInputStream(exchange.responseBody())) + .build(); + } + + @Override + public HttpExchange newExchange(HttpRequest request, RequestOptions options) throws IOException { + var resolvedInterceptors = options.resolveInterceptors(interceptors); + + // Allow interceptors to modify the request preflight (add headers, query string, change body, etc). + HttpRequest modifiedRequest = applyBeforeRequest(resolvedInterceptors, request, options.context()); + + // Allow interceptors to completely intercept the request and provide a specific response. + HttpResponse preempted = applyPreemptRequest(resolvedInterceptors, modifiedRequest, options.context()); + if (preempted != null) { + try { + HttpResponse intercepted = applyInterceptResponse( + this, + resolvedInterceptors, + modifiedRequest, + options.context(), + preempted); + if (intercepted != null) { + preempted = intercepted; + } + return HttpExchange.newBufferedExchange(modifiedRequest, preempted); + } catch (IOException e) { + // IOE during preemption can be recovered from using onError. + HttpResponse recovery = applyOnError(this, resolvedInterceptors, modifiedRequest, options.context(), e); + if (recovery != null) { + return HttpExchange.newBufferedExchange(modifiedRequest, recovery); + } + throw e; + } + } + + try { + return createManagedExchange(modifiedRequest, options.context(), resolvedInterceptors); + } catch (IOException e) { + HttpResponse recovery = applyOnError(this, resolvedInterceptors, modifiedRequest, options.context(), e); + if (recovery != null) { + return HttpExchange.newBufferedExchange(modifiedRequest, recovery); + } + throw e; + } + } + + private HttpExchange createManagedExchange( + HttpRequest request, + Context context, + List resolvedInterceptors + ) throws IOException { + Route route = Route.from(request.uri(), proxyConfiguration); + HttpConnection conn = connectionPool.acquire(route); + try { + HttpExchange baseExchange = conn.newExchange(request); + return new ManagedHttpExchange(baseExchange, + conn, + connectionPool, + request, + context, + resolvedInterceptors, + this); + } catch (IOException e) { + connectionPool.evict(conn, true); + throw e; + } + } + + private HttpRequest applyBeforeRequest(List resolved, HttpRequest request, Context context) + throws IOException { + HttpRequest modified = request; + for (HttpInterceptor interceptor : resolved) { + modified = interceptor.beforeRequest(this, modified, context); + } + return modified; + } + + private HttpResponse applyPreemptRequest(List resolved, HttpRequest request, Context context) + throws IOException { + for (HttpInterceptor interceptor : resolved) { + HttpResponse response = interceptor.preemptRequest(this, request, context); + if (response != null) { + return response; + } + } + return null; + } + + static HttpResponse applyInterceptResponse( + HttpClient client, + List resolved, + HttpRequest request, + Context context, + HttpResponse response + ) throws IOException { + HttpResponse current = response; + // iterate backward + for (int i = resolved.size() - 1; i >= 0; i--) { + HttpResponse replacement = resolved.get(i).interceptResponse(client, request, context, current); + if (replacement != null) { + current = replacement; + } + } + return current == response ? null : current; + } + + static HttpResponse applyOnError( + HttpClient client, + List resolved, + HttpRequest request, + Context context, + IOException exception + ) throws IOException { + // iterate backward + for (int i = resolved.size() - 1; i >= 0; i--) { + HttpResponse recovery = resolved.get(i).onError(client, request, context, exception); + if (recovery != null) { + return recovery; + } + } + return null; + } + + private HttpResponse sendWithTimeout(HttpRequest request, RequestOptions options, Duration timeout) + throws IOException { + // Run the blocking operation in its own virtual thread + Future future = executorService.submit(() -> sendInternal(request, options)); + + try { + return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + future.cancel(true); + throw new IOException(String.format( + "Request to `%s` exceeded request timeout of %s seconds", + request.uri().getHost(), + timeout.toSeconds()), e); + } catch (InterruptedException e) { + // The calling thread was interrupted while waiting + Thread.currentThread().interrupt(); + throw new IOException("Interrupted while waiting for HTTP request to complete to `" + + request.uri().getHost() + '`', e); + } catch (ExecutionException e) { + throw unwrap(e); + } + } + + private static IOException unwrap(ExecutionException e) throws IOException { + var cause = e.getCause(); + return switch (cause) { + case IOException io -> throw io; + case RuntimeException re -> throw re; + case Error err -> throw err; + case null -> new IOException("Unexpected exception", e); + default -> new IOException("Unexpected exception", cause); + }; + } + + @Override + public void close() throws IOException { + executorService.close(); + connectionPool.close(); + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java new file mode 100644 index 000000000..529dff7f1 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java @@ -0,0 +1,34 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.io.Closeable; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * InputStream wrapper that runs a callback when the stream is closed rather than closing the provided delegate. + * + *

The close callback is invoked at most once, and can be safely closed from any thread. + */ +public final class DelegatedClosingInputStream extends FilterInputStream { + private final Closeable onClose; + private final AtomicBoolean closed = new AtomicBoolean(false); + + public DelegatedClosingInputStream(InputStream delegate, Closeable onClose) { + super(delegate); + this.onClose = onClose; + } + + @Override + public void close() throws IOException { + if (closed.compareAndSet(false, true)) { + onClose.close(); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java new file mode 100644 index 000000000..1283e9619 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java @@ -0,0 +1,34 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.io.Closeable; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * OutputStream wrapper that runs a callback when the stream is closed rather than closing the delegate. + * + *

The close callback is invoked at most once, and can be safely closed from any thread. + */ +public final class DelegatedClosingOutputStream extends FilterOutputStream { + private final Closeable onClose; + private final AtomicBoolean closed = new AtomicBoolean(); + + public DelegatedClosingOutputStream(OutputStream delegate, Closeable onClose) { + super(delegate); + this.onClose = onClose; + } + + @Override + public void close() throws IOException { + if (closed.compareAndSet(false, true)) { + onClose.close(); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpClient.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpClient.java new file mode 100644 index 000000000..1983dca57 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpClient.java @@ -0,0 +1,246 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Objects; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.client.connection.ConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; + +/** + * Blocking, virtual-thread-friendly HTTP client. + * + *

This client supports both simple ({@link #send(HttpRequest)}) and bidirectional streaming + * ({@link #newExchange(HttpRequest)}) request/response patterns. Both return streaming responses. + * + *

The client is intentionally minimal. Behavior can be layered on top of the client via {@link HttpInterceptor}s. + */ +public interface HttpClient extends AutoCloseable { + /** + * Sends a request and returns a streaming response. + * + *

This is a convenience method that: + *

    + *
  1. Creates an {@link HttpExchange}
  2. + *
  3. Writes the request body (if present)
  4. + *
  5. Returns an {@link HttpResponse} with a streaming body
  6. + *
+ * + *

The response body streams directly from the socket. The caller must close the response body stream when + * done to release the connection back to the pool. + * + *

Interceptors can modify the request, short-circuit execution, retry on errors, or replace the response. + * + * @param request the HTTP request to send + * @return the HTTP response with streaming body + * @throws IOException if the request fails + */ + default HttpResponse send(HttpRequest request) throws IOException { + return send(request, RequestOptions.defaults()); + } + + /** + * Send a request with request options. + * + * @param request request to send. + * @param options options to apply. + * @return the HTTP response + * @throws IOException if the request fails + */ + HttpResponse send(HttpRequest request, RequestOptions options) throws IOException; + + /** + * Create a streaming exchange. + * + *

This is a low-level API that gives full control over request/response streams. + * The caller is responsible for: + *

    + *
  • Writing the request body via {@link HttpExchange#requestBody()} and closing it
  • + *
  • Reading the response body via {@link HttpExchange#responseBody()}
  • + *
  • Closing the exchange when done (or relying on auto-close when both streams close)
  • + *
+ * + *

IMPORTANT: Any body set on the {@link HttpRequest} is NOT automatically written. You must write the + * request body manually via {@link HttpExchange#requestBody()}. However, the Content-Length header, if present, + * on the request _is_ sent as a header automatically, so you must write the same number of bytes. + * Use {@link #send(HttpRequest)} if you want automatic request body handling. + * + *

Interceptors work with {@code exchange()}, but with limitations: + *

    + *
  • {@code interceptResponse} can see headers/status and replace response, but cannot safely retry
  • + *
  • Use {@code context.isModifiable()} to check if retry is safe
  • + *
+ * + * @param request the HTTP request + * @return a streaming exchange + * @throws IOException if the exchange cannot be created + */ + default HttpExchange newExchange(HttpRequest request) throws IOException { + return newExchange(request, RequestOptions.defaults()); + } + + /** + * Create a streaming exchange with options. + * + *

IMPORTANT: Any body set on the {@link HttpRequest} is NOT automatically written. You must write the + * request body manually via {@link HttpExchange#requestBody()}. + * + * @param request the HTTP request + * @param options options to apply + * @return a streaming exchange + * @throws IOException if the exchange cannot be created + * @see #newExchange(HttpRequest) + */ + HttpExchange newExchange(HttpRequest request, RequestOptions options) throws IOException; + + @Override + void close() throws IOException; + + /** + * Builder to create a new default HTTP client. + */ + static Builder builder() { + return new Builder(); + } + + /** + * Builder used to create a default HTTP client implementation. + */ + final class Builder { + ConnectionPool connectionPool; + Duration requestTimeout; + final Deque interceptors = new ArrayDeque<>(); + ProxyConfiguration proxyConfiguration; + + private Builder() {} + + /** + * Add an interceptor to customize request/response handling. + * + * @param interceptor the interceptor to add + * @return this builder + */ + public Builder addInterceptor(HttpInterceptor interceptor) { + interceptors.add(Objects.requireNonNull(interceptor, "interceptor")); + return this; + } + + /** + * Add an interceptor to the front of the list of interceptors ot apply. + * + * @param interceptor the interceptor to add to the front. + * @return this builder + * @see #addInterceptor(HttpInterceptor) + */ + public Builder addInterceptorFirst(HttpInterceptor interceptor) { + interceptors.addFirst(Objects.requireNonNull(interceptor, "interceptor")); + return this; + } + + /** + * Set a custom connection pool. + * + * @param pool the connection pool to use + * @return this builder + */ + public Builder connectionPool(ConnectionPool pool) { + this.connectionPool = pool; + return this; + } + + /** + * Set total request timeout including redirects and retries (default: none). + * + *

If set, the entire buffered request (including any interceptor retries, + * redirects, and authentication flows) must complete within this duration, + * or an {@link IOException} is thrown. + * + *

Scope: This timeout only applies to {@link HttpClient#send} calls + * (buffered requests). Streaming {@link HttpClient#newExchange} calls are not + * bounded by this timeout since the caller controls when to read/write. + * + *

Implementation: Timeout is enforced via {@link Thread#interrupt()}. + * Interceptors and underlying I/O must be interruptible for the timeout to be + * effective. Code that swallows interrupts may delay the actual abort. + * + *

If not set (null), requests have no overall timeout and are only limited by + * the connect and read timeouts. + * + * @param timeout total request timeout duration, or null for no timeout + * @return this builder + * @throws IllegalArgumentException if timeout is negative or zero + */ + public Builder requestTimeout(Duration timeout) { + if (timeout != null && (timeout.isNegative() || timeout.isZero())) { + throw new IllegalArgumentException("requestTimeout must be positive or null: " + timeout); + } + this.requestTimeout = timeout; + return this; + } + + /** + * Set proxy configuration for all connections made by this client. + * + *

When configured, all HTTP requests will be routed through the proxy + * unless the target host matches one of the non-proxy hosts. + * + *

For HTTPS requests, the client establishes a CONNECT tunnel through + * the proxy, then performs TLS handshake through the tunnel. + * + *

For HTTP requests, the client connects to the proxy and sends + * requests with absolute URIs. + * + * @param proxy the proxy configuration, or null for direct connections + * @return this builder + * @see ProxyConfiguration + */ + public Builder proxy(ProxyConfiguration proxy) { + this.proxyConfiguration = proxy; + return this; + } + + /** + * Set proxy configuration using a URI string. + * + *

Convenience method that creates an HTTP proxy configuration from + * a URI string. For more advanced configuration (authentication, + * bypass rules, SOCKS proxy), use {@link #proxy(ProxyConfiguration)}. + * + * @param proxyUri the proxy URI (e.g., {@code "http://proxy.example.com:8080"}) + * @return this builder + * @throws IllegalArgumentException if proxyUri is invalid + */ + public Builder proxy(String proxyUri) { + if (proxyUri == null) { + this.proxyConfiguration = null; + } else { + this.proxyConfiguration = ProxyConfiguration.builder() + .proxyUri(proxyUri) + .type(ProxyConfiguration.ProxyType.HTTP) + .build(); + } + return this; + } + + /** + * Build the HTTP client. + * + * @return a new HTTP client instance + * @throws IllegalStateException if the configuration is invalid + */ + public HttpClient build() { + if (connectionPool == null) { + connectionPool = HttpConnectionPool.builder().build(); + } + return new DefaultHttpClient(this); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java new file mode 100644 index 000000000..d70300105 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java @@ -0,0 +1,170 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.api.HttpVersion; + +/** + * HTTP request/response exchange. + * + *

Lifecycle: + * The exchange automatically closes when both the request and response streams are closed. + * Using try-with-resources on the exchange is recommended as a safety net, but not strictly required if both streams + * are properly closed. The {@link #close()} method of an HttpExchange implementation MUST be idempotent and ignore + * successive calls to close(). + * + *

Protocol-Specific Behavior: + *

    + *
  • HTTP/1.1: Sequential only. Request body must be fully written and closed before response can be read. + * True bidirectional streaming is NOT supported. Not thread-safe.
  • + *
  • HTTP/2: Full bidirectional streaming. Can read response while writing request. + * Thread-safe for concurrent read/write from separate threads.
  • + *
+ * + *

Usage Pattern with try-with-resources (recommended): + * {@snippet : + * try (HttpExchange exchange = client.newExchange(request)) { + * try (OutputStream out = exchange.requestBody()) { + * out.write(data); + * } + * int status = exchange.responseStatusCode(); + * try (InputStream in = exchange.responseBody()) { + * byte[] body = in.readAllBytes(); + * } + * } + * } + * + *

Usage Pattern for hand-off (streams managed separately): + * {@snippet : + * // Exchange auto-closes when BOTH streams are closed + * HttpExchange exchange = client.newExchange(request); + * // Hand off to different parts of the application + * sendToWriter(exchange.requestBody()); // Writer closes when done + * sendToReader(exchange.responseBody()); // Reader closes when done + * } + */ +public interface HttpExchange extends AutoCloseable { + /** + * Create a new buffered HTTP exchange where the response is already available and request does not need to + * be sent. + * + * @param request Request that was sent or that was intercepted. + * @param response Response to return. + * @return the buffered HttpExchange. + */ + static HttpExchange newBufferedExchange(HttpRequest request, HttpResponse response) { + return new BufferedHttpExchange(request, response); + } + + /** + * Returns the HTTP request associated with this exchange. + * + *

For exchanges created by {@link HttpClient}, this returns the request after + * interceptors have been applied (the "effective" request). + * + * @return the HTTP request + */ + HttpRequest request(); + + /** + * Where to write the request body. Blocks on flow control. + * + *

Closing this stream signals the end of the request body. For HTTP/2, closing this stream while the response + * stream is also closed will automatically close the exchange. + * + * @return request body stream + */ + OutputStream requestBody(); + + /** + * HTTP version from response. Blocks until received. + * + *

For HTTP/1.x connections, this returns the version from the response + * status line (HTTP/1.0 or HTTP/1.1). For HTTP/2, always returns HTTP/2. + * + * @return HTTP response version + */ + HttpVersion responseVersion() throws IOException; + + /** + * Response status code. Blocks until received. + * + *

IMPORTANT: On HTTP/1.1, this will block until the request body + * is fully written and closed. + * + * @return response status code + */ + int responseStatusCode() throws IOException; + + /** + * Read from response body. Blocks until data available. + * + *

IMPORTANT: On HTTP/1.1, this will block until the request body + * is fully written and closed. True bidirectional streaming requires HTTP/2. + * + *

Closing this stream will automatically close the exchange for HTTP/1.1. For HTTP/2, closing this stream + * while the request stream is also closed will automatically close the exchange. + * + * @return the response input stream to read. + */ + InputStream responseBody() throws IOException; + + /** + * Response headers. Blocks until received. + * + *

IMPORTANT: On HTTP/1.1, this will block until the request body + * is fully written and closed. + * + * @return HTTP response headers. + */ + HttpHeaders responseHeaders() throws IOException; + + /** + * Get trailer headers if any were received. + * + *

Trailers are headers sent after the message body. They are supported in: + *

    + *
  • HTTP/1.1: Via chunked transfer encoding (RFC 7230 Section 4.1.2)
  • + *
  • HTTP/2: Via HEADERS frame after DATA with END_STREAM (RFC 9113 Section 8.1)
  • + *
+ * + *

IMPORTANT: Trailers are only available after the entire response body + * has been read. Calling this before the body is fully consumed returns null. + * + * @return trailer headers, or null if no trailers were received + */ + default HttpHeaders responseTrailerHeaders() { + return null; + } + + /** + * Check if this exchange supports true bidirectional streaming. + * Returns true for HTTP/2, false for HTTP/1.1. + * + *

If false, the request body must be fully written and closed before + * attempting to read the response. + * + * @return true if the exchange supports bidirectional streaming. + */ + default boolean supportsBidirectionalStreaming() { + return false; + } + + /** + * {@inheritDoc} + * + *

This method is idempotent and may be called multiple times safely. + * Subsequent calls after the first have no effect. + */ + @Override + void close() throws IOException; +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpInterceptor.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpInterceptor.java new file mode 100644 index 000000000..1099be34a --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpInterceptor.java @@ -0,0 +1,205 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.io.IOException; +import software.amazon.smithy.java.context.Context; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; + +/** + * Interceptor for HTTP requests and responses. + * + *

Interceptors enable cross-cutting concerns such as logging, metrics, redirects, authentication, caching, retries, + * request/response pre-flight transformations, etc. + * + *

Execution Order

+ * + *

For a chain of interceptors [A, B, C]: + *

    + *
  • {@link #beforeRequest} - forward order: A → B → C
  • + *
  • {@link #preemptRequest} - forward order: A → B → C (stops on first non-null)
  • + *
  • {@link #interceptResponse} - reverse order: C → B → A
  • + *
  • {@link #onError} - reverse order: C → B → A (stops on first non-null)
  • + *
+ * + *

Execution Flow

+ * + *

The following diagram shows the execution flow for a request: + * + *

+ *   beforeRequest (A → B → C)
+ *          │
+ *          ▼
+ *   preemptRequest (A → B → C) ──── returns response? ────┐
+ *          │                                              │
+ *          │ null                                         │
+ *          ▼                                              │
+ *   ┌─────────────────┐                                   │
+ *   │ Network Request │                                   │
+ *   └────────┬────────┘                                   │
+ *            │                                            │
+ *            ▼                                            ▼
+ *   interceptResponse (C → B → A) ◄───────────────────────┘
+ *            │
+ *            │ throws IOException?
+ *            ▼
+ *   onError (C → B → A) ──── returns recovery? ──── return recovery
+ *            │
+ *            │ null
+ *            ▼
+ *      propagate exception
+ * 
+ * + *

Error Handling

+ * + *

If any interceptor throws an {@link java.io.IOException}: + *

    + *
  • From {@link #beforeRequest} or {@link #preemptRequest}: propagates directly to caller
  • + *
  • From network request: passed to {@link #onError} for recovery
  • + *
  • From {@link #interceptResponse}: passed to {@link #onError} for recovery
  • + *
  • From {@link #onError}: propagates directly to caller
  • + *
+ * + *

This allows interceptors that perform retries in {@link #interceptResponse} to have their + * failures handled by error recovery interceptors. + * + *

Thread Safety

+ * + *

Interceptor implementations must be thread-safe. The same interceptor instance may be called concurrently + * from multiple threads for different requests. However, for a single request, all callbacks are invoked sequentially + * on the same thread. This means request-scoped state stored in the {@link Context} can be accessed without + * synchronization. + * + *

Interceptors may block freely in any callback method. No locks are held when interceptors are invoked, so + * blocking will not cause deadlocks or contention with other requests. + * + *

Example

+ * + * {@snippet : + * public class LoggingInterceptor implements HttpInterceptor { + * @Override + * public HttpRequest beforeRequest(HttpClient client, HttpRequest request, Context context) { + * System.out.println("Request: " + request.method() + " " + request.uri()); + * return request; + * } + * + * @Override + * public HttpResponse interceptResponse(HttpClient client, HttpRequest request, + * Context context, HttpResponse response) { + * System.out.println("Response: " + response.statusCode()); + * return response; + * } + * } + * } + * + * @see HttpClient.Builder#addInterceptor(HttpInterceptor) + * @see RequestOptions.Builder#addInterceptor(HttpInterceptor) + */ +public interface HttpInterceptor { + /** + * Called before sending the request and can modify the request pre-flight. + * + *

Use this hook to add headers (authentication, tracing), modify URIs, or transform the request body. + * + *

Errors thrown from this method propagate directly to the caller without passing through {@link #onError}. + * + * @param client the HTTP client (can be used to make additional requests) + * @param request the outgoing request + * @param context request-scoped context for passing data between interceptors + * @return the modified request, or the original request unchanged + */ + default HttpRequest beforeRequest(HttpClient client, HttpRequest request, Context context) throws IOException { + return request; + } + + /** + * Called to potentially handle the request without making a network call. + * + *

Use this hook to implement caching, mock responses for testing, or short-circuit requests that can be + * handled locally. + * + *

Errors thrown from this method propagate directly to the caller without passing through {@link #onError}. + * + * @param client the HTTP client (can be used for cache validation requests) + * @param request the outgoing request + * @param context request-scoped context for passing data between interceptors + * @return a response to use instead of making a network call, or null to proceed normally + * @throws IOException if an I/O error occurs (propagates directly to caller) + */ + default HttpResponse preemptRequest(HttpClient client, HttpRequest request, Context context) throws IOException { + return null; + } + + /** + * Called after receiving the response status and headers. + * + *

Works for both {@link HttpClient#send} (buffered) and {@link HttpClient#newExchange} (streaming): + *

    + *
  • send(): Called immediately after network response is received
  • + *
  • exchange(): Called lazily when caller first accesses response
  • + *
+ * + *

This hook can: + *

    + *
  • Return the given response to keep the original response unchanged
  • + *
  • Return a different response to replace it (e.g., for retries)
  • + *
  • Block as needed by calling {@code client.send()} to retry the request
  • + *
+ * + *

Error handling: If this method throws an {@link IOException}, it is passed to {@link #onError} for + * potential recovery. This allows retry interceptors to have their failures handled by error recovery interceptors. + * + *

Warning for streaming exchanges: When used with {@code exchange()}, the response body is a live + * stream. Reading the body will consume it, making it unavailable to the caller. If you read the body, you must + * provide a replacement response. Retrying is also dangerous since the request body may have already been streamed. + * + * @param client the HTTP client (can be used to retry the request) + * @param request the original request + * @param context request-scoped context for passing data between interceptors + * @param response the response received from the server (or previous interceptor) + * @return the non-null replacement response + * @throws IOException if an I/O error occurs (that {@link #onError} did not recover from) + */ + default HttpResponse interceptResponse( + HttpClient client, + HttpRequest request, + Context context, + HttpResponse response + ) throws IOException { + return response; + } + + /** + * Called when an exception occurs during request execution or response interception. + * + *

This method is invoked when: + *

    + *
  • The network request fails
  • + *
  • {@link #interceptResponse} throws an {@link IOException}
  • + *
+ * + *

Use this hook to implement fallback responses, retry logic with backoff, or circuit breaker patterns. + * + *

Note: Errors thrown from this method propagate directly to the caller. There is no further error + * recovery after {@code onError}. + * + * @param client the HTTP client (can be used to retry the request) + * @param request the request that failed + * @param context request-scoped context for passing data between interceptors + * @param exception the exception that occurred during execution + * @return a recovery response, or null to propagate the exception to the caller + * @throws IOException if an I/O error occurs (propagates directly to caller) + */ + default HttpResponse onError( + HttpClient client, + HttpRequest request, + Context context, + IOException exception + ) throws IOException { + return null; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java new file mode 100644 index 000000000..c017b818a --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java @@ -0,0 +1,244 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import software.amazon.smithy.java.context.Context; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.ConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpConnection; +import software.amazon.smithy.java.io.datastream.DataStream; + +/** + * HttpExchange wrapper that manages connection pooling and interceptor hooks. + * + *

Connection Management

+ * + *

The wrapper tracks errors that occur during the exchange: + *

    + *
  • On successful close: connection is released back to pool for reuse
  • + *
  • On error during exchange: connection is evicted (not reused)
  • + *
  • On error during close: connection is evicted
  • + *
+ * + *

Interceptor Behavior

+ * + *

The interceptResponse() hook is called lazily when the response is first accessed (via statusCode(), + * responseHeaders(), or responseBody()). This ensures interceptors see the response even for streaming exchanges. + * + *

Important: If interceptors read the response body from the provided HttpResponse, they MUST provide a + * replacement response with a new body. Otherwise, the body stream will be consumed and unavailable to the caller. + * + *

Thread Safety

+ * + *

This class is NOT thread-safe. + */ +final class ManagedHttpExchange implements HttpExchange { + + // No need to allocate or track closed with a volatile like the built-in version does. + private static final OutputStream VERY_NULL_OUTPUT_STREAM = new OutputStream() { + @Override + public void write(int b) {} + }; + + // Connection management + private final HttpExchange delegate; + private final HttpConnection connection; + private final ConnectionPool pool; + + // Interceptor support + private final HttpRequest request; + private final Context context; + private final List interceptors; + private final HttpClient client; + + // State + private boolean closed; + private boolean connectionHandled; // true after pool.release() or pool.evict() called + private boolean errored; + private boolean intercepted; + private HttpResponse interceptedResponse; + private InputStream responseIn; + + ManagedHttpExchange( + HttpExchange delegate, + HttpConnection connection, + ConnectionPool pool, + HttpRequest request, + Context context, + List interceptors, + HttpClient client + ) { + this.delegate = delegate; + this.connection = connection; + this.pool = pool; + this.request = request; + this.context = context; + this.interceptors = interceptors; + this.client = client; + } + + @Override + public HttpRequest request() { + return request; + } + + @Override + public OutputStream requestBody() { + return delegate.requestBody(); + } + + @Override + public InputStream responseBody() throws IOException { + if (responseIn != null) { + return responseIn; + } + + try { + ensureIntercepted(); + InputStream body = interceptedResponse != null + ? interceptedResponse.body().asInputStream() + : delegate.responseBody(); + // Wrap so closing the response body releases the connection to the pool + responseIn = new DelegatedClosingInputStream(body, this::close); + return responseIn; + } catch (IOException e) { + errored = true; + throw e; + } + } + + @Override + public HttpHeaders responseHeaders() throws IOException { + try { + ensureIntercepted(); + return interceptedResponse != null ? interceptedResponse.headers() : delegate.responseHeaders(); + } catch (IOException e) { + errored = true; + throw e; + } + } + + @Override + public int responseStatusCode() throws IOException { + try { + ensureIntercepted(); + return interceptedResponse != null ? interceptedResponse.statusCode() : delegate.responseStatusCode(); + } catch (IOException e) { + errored = true; + throw e; + } + } + + @Override + public HttpVersion responseVersion() throws IOException { + try { + ensureIntercepted(); + return interceptedResponse != null ? interceptedResponse.httpVersion() : delegate.responseVersion(); + } catch (IOException e) { + errored = true; + throw e; + } + } + + @Override + public boolean supportsBidirectionalStreaming() { + return delegate.supportsBidirectionalStreaming(); + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + closed = true; + + // Drain response body before releasing connection (required for HTTP/1.1 connection reuse) + try { + if (responseIn != null) { + responseIn.transferTo(VERY_NULL_OUTPUT_STREAM); + } + } catch (IOException ignored) { + // Drain failed, so the connection cannot be reused safely + errored = true; + } + + try { + delegate.close(); + } catch (IOException e) { + errored = true; + throw e; + } finally { + // Ensure connection is returned to pool exactly once + if (!connectionHandled) { + connectionHandled = true; + if (errored) { + pool.evict(connection, true); + } else { + pool.release(connection); + } + } + } + } + + /** + * Call interceptResponse() once, when response is first accessed. + * + *

This method eagerly reads status code, headers, and obtains the body stream + * from the delegate to build an HttpResponse for interceptors. If interceptors + * replace the response, subsequent calls use the replacement. + * + *

The intercepted flag is set before calling delegate methods. If delegate + * methods throw, subsequent calls will skip interception and call delegate + * directly, allowing partial recovery. + * + *

If an interceptor throws an IOException, the error is passed to onError + * interceptors for potential recovery. + */ + private void ensureIntercepted() throws IOException { + if (intercepted) { + return; + } + intercepted = true; + + if (interceptors.isEmpty()) { + return; + } + + HttpResponse currentResponse = HttpResponse.builder() + .statusCode(delegate.responseStatusCode()) + .headers(delegate.responseHeaders()) + .body(DataStream.ofInputStream(delegate.responseBody())) + .build(); + + HttpResponse replacement; + try { + replacement = DefaultHttpClient.applyInterceptResponse( + client, + interceptors, + request, + context, + currentResponse); + } catch (IOException e) { + HttpResponse recovery = DefaultHttpClient.applyOnError(client, interceptors, request, context, e); + if (recovery != null) { + interceptedResponse = recovery; + return; + } + throw e; + } + + if (replacement != null) { + interceptedResponse = replacement; + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/NonClosingOutputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/NonClosingOutputStream.java new file mode 100644 index 000000000..7c3b6d597 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/NonClosingOutputStream.java @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * OutputStream wrapper that prevents closing the underlying stream. + * + *

Used for HTTP/1.1 request bodies where we don't want to close the socket when the request body is done. + */ +public final class NonClosingOutputStream extends OutputStream { + private final OutputStream delegate; + private boolean closed = false; + + public NonClosingOutputStream(OutputStream delegate) { + this.delegate = delegate; + } + + @Override + public void write(int b) throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + delegate.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + delegate.write(b, off, len); + } + + @Override + public void flush() throws IOException { + if (!closed) { + delegate.flush(); + } + } + + @Override + public void close() throws IOException { + if (!closed) { + closed = true; + delegate.flush(); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxyConfiguration.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxyConfiguration.java new file mode 100644 index 000000000..fed65987c --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxyConfiguration.java @@ -0,0 +1,240 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.net.URI; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +/** + * Proxy configuration for HTTP connections. + * + *

Supports HTTP and SOCKS proxies with optional authentication. + * + * @param proxyUri Proxy server URI. + * @param type Type of proxy. + * @param username Optional username for proxy authentication. + * @param password Optional password for proxy authentication. + * @param nonProxyHosts Hosts that should bypass the proxy. Supports wildcards: "*.internal.example.com". + */ +public record ProxyConfiguration( + URI proxyUri, + ProxyType type, + String username, + String password, + List nonProxyHosts) { + public ProxyConfiguration { + Objects.requireNonNull(proxyUri, "proxyUri cannot be null"); + Objects.requireNonNull(type, "type cannot be null"); + nonProxyHosts = nonProxyHosts == null + ? List.of() + : nonProxyHosts.stream().map(s -> s.toLowerCase(Locale.ROOT)).toList(); + } + + /** + * Check if a given host should bypass this proxy. + * + * @param host hostname to check + * @return true if host should bypass proxy + */ + public boolean shouldBypass(String host) { + if (host == null || nonProxyHosts.isEmpty()) { + return false; + } + String lowerHost = host.toLowerCase(Locale.ROOT); + for (String pattern : nonProxyHosts) { + if (matchesPattern(lowerHost, pattern)) { + return true; + } + } + return false; + } + + private boolean matchesPattern(String host, String pattern) { + // Simple wildcard matching (host and pattern are already lowercase) + if (pattern.startsWith("*.")) { + String suffix = pattern.substring(1); // Remove '*', keep the dot + return host.endsWith(suffix); + } + return host.equals(pattern); + } + + /** + * Returns the proxy hostname. + * + * @return the hostname from the proxy URI + */ + public String hostname() { + return proxyUri.getHost(); + } + + /** + * Returns the proxy port. + * + *

If the port is not specified in the URI, returns the default port + * for the proxy type: 8080 for HTTP/HTTPS, 1080 for SOCKS. + * + * @return the proxy port + */ + public int port() { + int port = proxyUri.getPort(); + if (port != -1) { + return port; + } + // Default ports + return switch (type) { + case HTTP, HTTPS -> 8080; + case SOCKS4, SOCKS5 -> 1080; + }; + } + + /** + * Returns whether proxy authentication is configured. + * + * @return true if username is set + */ + public boolean requiresAuth() { + return username != null; + } + + /** + * Proxy protocol type. + */ + public enum ProxyType { + /** HTTP proxy (CONNECT tunnel for HTTPS) */ + HTTP, + + /** HTTPS proxy */ + HTTPS, + + /** SOCKS4 proxy */ + SOCKS4, + + /** SOCKS5 proxy */ + SOCKS5 + } + + /** + * Builder for ProxyConfiguration. + */ + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private URI proxyUri; + private ProxyType type = ProxyType.HTTP; + private String username; + private String password; + private List nonProxyHosts = List.of(); + + private Builder() {} + + /** + * Sets the proxy server URI. + * + * @param proxyUri the proxy URI (e.g., {@code http://proxy.example.com:8080}) + * @return this builder + */ + public Builder proxyUri(URI proxyUri) { + this.proxyUri = proxyUri; + return this; + } + + /** + * Sets the proxy server URI from a string. + * + * @param proxyUri the proxy URI string + * @return this builder + * @throws IllegalArgumentException if the URI is invalid + */ + public Builder proxyUri(String proxyUri) { + return proxyUri(URI.create(proxyUri)); + } + + /** + * Sets the proxy type (default: HTTP). + * + * @param type the proxy protocol type + * @return this builder + */ + public Builder type(ProxyType type) { + this.type = type; + return this; + } + + /** + * Sets the username for proxy authentication. + * + * @param username the authentication username + * @return this builder + */ + public Builder username(String username) { + this.username = username; + return this; + } + + /** + * Sets the password for proxy authentication. + * + * @param password the authentication password + * @return this builder + */ + public Builder password(String password) { + this.password = password; + return this; + } + + /** + * Sets both username and password for proxy authentication. + * + * @param username the authentication username + * @param password the authentication password + * @return this builder + */ + public Builder credentials(String username, String password) { + this.username = username; + this.password = password; + return this; + } + + /** + * Sets hosts that should bypass the proxy. + * + *

Supports wildcard patterns: {@code *.internal.example.com} matches + * any subdomain of {@code internal.example.com}. + * + * @param nonProxyHosts list of hostnames or patterns to bypass + * @return this builder + */ + public Builder nonProxyHosts(List nonProxyHosts) { + this.nonProxyHosts = List.copyOf(nonProxyHosts); + return this; + } + + /** + * Sets hosts that should bypass the proxy. + * + * @param nonProxyHosts hostnames or patterns to bypass + * @return this builder + * @see #nonProxyHosts(List) + */ + public Builder nonProxyHosts(String... nonProxyHosts) { + return nonProxyHosts(List.of(nonProxyHosts)); + } + + /** + * Builds the proxy configuration. + * + * @return the configured ProxyConfiguration + * @throws NullPointerException if proxyUri is null + */ + public ProxyConfiguration build() { + return new ProxyConfiguration(proxyUri, type, username, password, nonProxyHosts); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/RequestOptions.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/RequestOptions.java new file mode 100644 index 000000000..923f7a0dd --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/RequestOptions.java @@ -0,0 +1,188 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import software.amazon.smithy.java.context.Context; + +/** + * Per-request configuration options for HTTP requests. + * + *

Example usage: + * {@snippet : + * RequestOptions options = RequestOptions.builder() + * .putContext(TRACE_ID_KEY, traceId) + * .addInterceptor(new LoggingInterceptor()) + * .build(); + * + * HttpResponse response = client.send(request, options); + * } + * + * @see HttpClient#send(software.amazon.smithy.java.http.api.HttpRequest, RequestOptions) + * @see HttpClient#newExchange(software.amazon.smithy.java.http.api.HttpRequest, RequestOptions) + * + * @param context Request context used with interceptors + * @param requestTimeout Per-request timeout override, or null to use client default. + * @param interceptors Interceptors to add to the request in addition to client-wide interceptors. + */ +public record RequestOptions(Context context, Duration requestTimeout, List interceptors) { + + public RequestOptions { + Objects.requireNonNull(context, "context"); + Objects.requireNonNull(interceptors, "interceptors"); + if (requestTimeout != null && (requestTimeout.isNegative() || requestTimeout.isZero())) { + throw new IllegalArgumentException("requestTimeout must be positive or null: " + requestTimeout); + } + } + + /** + * Resolves the final list of interceptors by combining client and request interceptors. + * + *

Client interceptors are applied first, followed by request-specific interceptors. + * This ordering allows request interceptors to override or extend client behavior. + * + * @param clientInterceptors interceptors configured on the HTTP client + * @return combined list with client interceptors first, then request interceptors + */ + public List resolveInterceptors(List clientInterceptors) { + if (clientInterceptors.isEmpty()) { + return interceptors; + } else if (interceptors.isEmpty()) { + return clientInterceptors; + } else { + List resolved = new ArrayList<>(interceptors.size() + clientInterceptors.size()); + resolved.addAll(clientInterceptors); + resolved.addAll(interceptors); + return resolved; + } + } + + /** + * Creates a new builder for RequestOptions. + * + * @return a new builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns default request options with an empty context and no interceptors. + * + * @return default request options + */ + public static RequestOptions defaults() { + return builder().build(); + } + + /** + * Builder for creating RequestOptions instances. + */ + public static final class Builder { + private Context context; + private Duration requestTimeout; + private List interceptors; + + private Builder() {} + + /** + * Sets the mutable request context. + * + *

The context can be used to pass request-scoped data to interceptors. + * + * @param context the context to use for this request + * @return this builder + */ + public Builder context(Context context) { + this.context = context; + return this; + } + + /** + * Adds a key-value pair to the request context. + * + *

Creates a new context if one hasn't been set. This is a convenience + * method for adding individual context values without creating a Context first. + * + * @param key the context key + * @param value the value to associate with the key + * @param the type of the context value + * @return this builder + */ + public Builder putContext(Context.Key key, T value) { + if (context == null) { + context = Context.create(); + } + this.context.put(key, value); + return this; + } + + /** + * Sets the request timeout for this specific request. + * + *

Overrides the client-level timeout. Set to null to use the client default. + * + * @param timeout the timeout duration, or null for client default + * @return this builder + */ + public Builder requestTimeout(Duration timeout) { + this.requestTimeout = timeout; + return this; + } + + /** + * Adds an interceptor to the request. + * + *

Request interceptors are applied after client-level interceptors. + * Multiple interceptors can be added and will be applied in the order added. + * + * @param interceptor the interceptor to add + * @return this builder + */ + public Builder addInterceptor(HttpInterceptor interceptor) { + if (interceptors == null) { + interceptors = new ArrayList<>(); + } + this.interceptors.add(interceptor); + return this; + } + + /** + * Sets the list of request interceptors, replacing any previously added. + * + * @param interceptors the interceptors to use for this request + * @return this builder + */ + public Builder interceptors(List interceptors) { + if (this.interceptors == null) { + this.interceptors = new ArrayList<>(interceptors); + } else { + this.interceptors.clear(); + this.interceptors.addAll(interceptors); + } + return this; + } + + /** + * Builds the RequestOptions instance. + * + * @return a new RequestOptions with the configured settings + */ + public RequestOptions build() { + // Take-and-replace to avoid defensive copies + Context ctx = context != null ? context : Context.create(); + context = null; + + List ints = interceptors != null ? interceptors : List.of(); + interceptors = null; + + return new RequestOptions(ctx, requestTimeout, ints); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStream.java new file mode 100644 index 000000000..30e23dc72 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStream.java @@ -0,0 +1,249 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * A buffered input stream like {@link java.io.BufferedInputStream}, but without synchronization. + */ +public final class UnsyncBufferedInputStream extends InputStream { + private final InputStream in; + private final byte[] buf; + private int pos; + private int limit; + private boolean closed; + + public UnsyncBufferedInputStream(InputStream in, int size) { + if (size <= 0) { + throw new IllegalArgumentException("Buffer size <= 0"); + } + this.in = in; + this.buf = new byte[size]; + } + + /** + * Fills the buffer with data from the underlying stream. + * + * @return the number of bytes read, or -1 if EOF + * @throws IOException if an I/O error occurs + */ + private int fill() throws IOException { + pos = 0; + int n = in.read(buf); + // Keep limit >= 0 so that "pos >= limit" comparisons work correctly after EOF + limit = Math.max(n, 0); + return n; + } + + @Override + public int read() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } else if (pos >= limit && fill() <= 0) { + return -1; + } else { + return buf[pos++] & 0xFF; + } + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + int n = 0; + + // First, drain the buffer + int avail = limit - pos; + if (avail > 0) { + int toCopy = Math.min(avail, len); + System.arraycopy(buf, pos, b, off, toCopy); + pos += toCopy; + off += toCopy; + len -= toCopy; + n += toCopy; + if (len == 0) { + return n; + } + } + + // If caller wants something large, bypass our buffer + if (len >= buf.length) { + int direct = in.read(b, off, len); + if (direct < 0) { + return n == 0 ? -1 : n; + } + return n + direct; + } + + // Otherwise, refill and copy from buffer + if (fill() <= 0) { + return n == 0 ? -1 : n; + } + + int toCopy = Math.min(limit - pos, len); + System.arraycopy(buf, pos, b, off, toCopy); + pos += toCopy; + return n + toCopy; + } + + @Override + public long skip(long n) throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } else if (n <= 0) { + return 0; + } + + long remaining = n; + + // First skip what's in the buffer + int avail = limit - pos; + if (avail > 0) { + long skipped = Math.min(avail, remaining); + pos += (int) skipped; + remaining -= skipped; + } + + // Skip in underlying stream only if needed + if (remaining > 0) { + long skippedUnderlying = in.skip(remaining); + if (skippedUnderlying > 0) { + remaining -= skippedUnderlying; + } + } + + return n - remaining; + } + + @Override + public int available() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + int avail = limit - pos; + if (avail < 0) { + avail = 0; + } + return avail + in.available(); + } + + @Override + public void close() throws IOException { + if (!closed) { + closed = true; + in.close(); + } + } + + // Optimized transferTo that doesn't allocate a new buffer. + @Override + public long transferTo(OutputStream out) throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + + // First drain what's already buffered + long transferred = 0; + int buffered = limit - pos; + if (buffered > 0) { + out.write(buf, pos, buffered); + pos = limit; + transferred = buffered; + } + + // Then stream the rest using _our_ buffer (super would allocate a buffer) + int n; + while ((n = in.read(buf)) != -1) { + out.write(buf, 0, n); + transferred += n; + } + return transferred; + } + + /** + * Reads a line terminated by CRLF or LF into the provided buffer. + * + *

This method is optimized for HTTP header parsing where lines are typically + * short and fit within a single buffer. + * + * @param dest buffer to read line into + * @param maxLength maximum allowed line length + * @return the number of bytes written to dest, or -1 if EOF with no data + * @throws IOException if an I/O error occurs or line exceeds maxLength or dest.length + */ + public int readLine(byte[] dest, int maxLength) throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + + int destPos = 0; + + for (;;) { + // Ensure buffer has data + if (pos >= limit && fill() <= 0) { + // EOF - return what we have + return destPos > 0 ? destPos : -1; + } + + // Scan buffer for line terminator - use locals for hot loop + int scanStart = pos; + int maxScan = Math.min(limit, pos + Math.min(maxLength - destPos + 1, dest.length - destPos)); + byte[] localBuf = buf; + + while (pos < maxScan) { + byte b = localBuf[pos]; + if (b == '\r' || b == '\n') { + // Copy scanned bytes to dest + int scannedLen = pos - scanStart; + if (scannedLen > 0) { + System.arraycopy(localBuf, scanStart, dest, destPos, scannedLen); + destPos += scannedLen; + } + pos++; + if (b == '\r') { + // Check for LF after CR + if (pos < limit || fill() > 0) { + if (localBuf[pos] == '\n') { + pos++; + } + } + } + return destPos; + } + pos++; + } + + // Copy scanned bytes to dest + int scannedLen = pos - scanStart; + if (scannedLen > 0) { + System.arraycopy(localBuf, scanStart, dest, destPos, scannedLen); + destPos += scannedLen; + } + + // Check if we hit the length limit without finding terminator + if (destPos > maxLength) { + throw new IOException("Line exceeds maximum length of " + maxLength); + } + if (destPos >= dest.length) { + throw new IOException("Line exceeds buffer size of " + dest.length); + } + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStream.java new file mode 100644 index 000000000..bd9f77348 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStream.java @@ -0,0 +1,139 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * A buffered output stream like {@link java.io.BufferedOutputStream}, but without synchronization. + */ +public final class UnsyncBufferedOutputStream extends OutputStream { + private final OutputStream out; + private final byte[] buf; + private int pos; + private boolean closed; + + /** + * Creates a buffered output stream with the specified buffer size. + * + * @param out the underlying output stream + * @param size the buffer size + */ + public UnsyncBufferedOutputStream(OutputStream out, int size) { + if (size <= 0) { + throw new IllegalArgumentException("Buffer size <= 0"); + } + this.out = out; + this.buf = new byte[size]; + } + + private void flushBuffer() throws IOException { + if (pos > 0) { + out.write(buf, 0, pos); + pos = 0; + } + } + + @Override + public void write(int b) throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + if (pos >= buf.length) { + flushBuffer(); + } + buf[pos++] = (byte) b; + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } else if (len >= buf.length) { + // If data is larger than buffer, flush and write directly + flushBuffer(); + out.write(b, off, len); + return; + } + + // If data won't fit in remaining buffer, flush first before copying to the buffer. + if (len > buf.length - pos) { + flushBuffer(); + } + + System.arraycopy(b, off, buf, pos, len); + pos += len; + } + + /** + * Writes an ASCII string directly to the buffer. + * Each character is cast to a byte (assumes ASCII/Latin-1 input). + * + * @param s the string to write + * @throws IOException if an I/O error occurs + */ + // we intentionally use the deprecated getBytes(int,int,byte[],int) since it's perfect for copying ascii. + @SuppressWarnings("deprecation") + public void writeAscii(String s) throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + int len = s.length(); + if (len == 0) { + return; + } + + int stringPosition = 0; + int bufLen = buf.length; + + // Work through the string bytes in chunks of bytes that can fit into the buffer. + while (stringPosition < len) { + int available = bufLen - pos; + if (available == 0) { + // We filled up the buffer, so flush and then continue to copy the next chunk + flushBuffer(); + available = bufLen; + } + + // Copy as many characters as will fit (or remaining string length, whichever is smaller) + int toCopy = Math.min(available, len - stringPosition); + s.getBytes(stringPosition, stringPosition + toCopy, buf, pos); + pos += toCopy; + stringPosition += toCopy; + } + } + + @Override + public void flush() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + flushBuffer(); + out.flush(); + } + + @Override + public void close() throws IOException { + if (!closed) { + try { + flushBuffer(); + } finally { + closed = true; + out.close(); + } + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/CloseReason.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/CloseReason.java new file mode 100644 index 000000000..33e2ca1fa --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/CloseReason.java @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +/** + * Reason why a connection was closed by the connection pool. + */ +public enum CloseReason { + /** + * Connection was idle too long. + * + *

Used when the pool's background cleanup removes idle connections. + */ + IDLE_TIMEOUT, + + /** + * Connection was closed unexpectedly. + * + *

The socket was closed by the peer, reset, or encountered an I/O error. + */ + UNEXPECTED_CLOSE, + + /** + * Connection couldn't be pooled because the pool was full. + * + *

The user returned the connection but the per-route pool was at capacity. + */ + POOL_FULL, + + /** + * Connection closed due to pool shutdown. + * + *

The pool is closing and all connections are being terminated. + */ + POOL_SHUTDOWN, + + /** + * User explicitly evicted the connection. + * + *

The user called {@link ConnectionPool#evict} with {@code isError=false}. + */ + EVICTED, + + /** + * User evicted the connection due to an error. + * + *

The user called {@link ConnectionPool#evict} with {@code isError=true}, + * indicating an error occurred during use. + */ + ERRORED +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/ConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/ConnectionPool.java new file mode 100644 index 000000000..6a5a8305c --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/ConnectionPool.java @@ -0,0 +1,68 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +import java.io.IOException; +import java.time.Duration; + +/** + * Connection pool for managing HTTP connections. + * + *

Pools connections by {@link Route}, validates health before reuse, and enforces connection limits. + * All methods must be thread-safe. + * + * @see HttpConnectionPool + */ +public interface ConnectionPool extends AutoCloseable { + /** + * Acquire a connection for the given route. + * + *

Returns a pooled connection if available and healthy, otherwise creates new. + * Blocks until a connection is available or limits are exceeded. + * + * @param route the route to connect to + * @return a usable connection + * @throws IOException if connection cannot be established + * @throws IllegalStateException if pool is closed + */ + HttpConnection acquire(Route route) throws IOException; + + /** + * Release a connection back to the pool for reuse. + * + *

Use for normal completion. Use {@link #evict} if connection is broken. + * + * @param connection the connection to release + */ + void release(HttpConnection connection); + + /** + * Evict a connection without returning it to the pool. + * + *

Use when the connection should not be reused. The connection is closed immediately. + * + * @param connection the connection to evict + * @param isError true if eviction is due to an error (IOException, protocol error, etc.), + * false for intentional eviction + */ + void evict(HttpConnection connection, boolean isError); + + /** + * Gracefully shut down, waiting for active connections to complete. + * + * @param gracePeriod maximum time to wait before force-closing + * @throws IOException if connections fail to close + */ + void shutdown(Duration gracePeriod) throws IOException; + + /** + * Close the pool and all connections. Idempotent. + * + * @throws IOException if connections fail to close + */ + @Override + void close() throws IOException; +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/ConnectionPoolListener.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/ConnectionPoolListener.java new file mode 100644 index 000000000..e6f900c1f --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/ConnectionPoolListener.java @@ -0,0 +1,75 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +/** + * Listener for connection pool lifecycle events. + * + *

Implement this interface to monitor connection pool activity for metrics collection, leak detection, logging, + * etc. All methods have empty default implementations, so you only need to override the events you care about. + * Listeners are called synchronously on the thread performing the pool operation. Implementations should be fast + * and non-blocking to avoid impacting pool performance. + * + *

Important: Do not modify connections

+ *

Listeners receive connection references for identification and metadata access only. Do NOT call + * {@link HttpConnection#close()}, {@link HttpConnection#newExchange}, or hold strong references to connections. + * + *

Connection Lifecycle

+ *
{@code
+ * New connection with successful use: onConnected → onAcquire(reused=false) → use → onReturn → (pooled, no close)
+ * New connection, pool full on return: onConnected → onAcquire(reused=false) → use → onReturn → onClosed
+ * Reused connection: onAcquire(reused=true) → use → onReturn → (pooled, no close)
+ * Connection with error: onAcquire → use → onClosed  (no onReturn, user evicted)
+ * Idle connection expires: (in pool) → onClosed
+ * Pool shutdown: (in pool) → onClosed
+ * }
+ * + * @see HttpConnectionPoolBuilder#addListener(ConnectionPoolListener) + */ +public interface ConnectionPoolListener { + /** + * Called when a new connection is fully established. + * + *

This is called after TCP connection (and TLS handshake for HTTPS) completes successfully, + * before the connection is handed to the caller. Called once per connection lifetime. + * + * @param connection the newly established connection + */ + default void onConnected(HttpConnection connection) {} + + /** + * Called when a connection is acquired from the pool. + * + *

This is called when a connection is handed to a caller, whether it's a newly created + * or reused pooled connection. Called each time a connection is acquired. + * + * @param connection the acquired connection + * @param reused true if this is a reused pooled connection, false if newly created + */ + default void onAcquire(HttpConnection connection, boolean reused) {} + + /** + * Called when a user returns a connection to the pool. + * + *

This indicates the user is done with the connection. The connection may be pooled for + * reuse or closed (if unhealthy or pool is full). If closed, {@link #onClosed} will also be called. + * + *

This is NOT called when a user evicts a connection - only {@link #onClosed} is called in that case. + * + * @param connection the returned connection + */ + default void onReturn(HttpConnection connection) {} + + /** + * Called when a connection is closed. + * + *

This is called whenever a connection is terminated, regardless of why. + * + * @param connection the closed connection + * @param reason why the connection was closed + */ + default void onClosed(HttpConnection connection, CloseReason reason) {} +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java new file mode 100644 index 000000000..111de0db0 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java @@ -0,0 +1,208 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +import java.io.IOException; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.IntFunction; + +/** + * Manages HTTP/1.1 connection pooling. + * + *

Pools idle connections per route using LIFO queues. Connections are + * validated before reuse and cleaned up when idle too long. + */ +final class H1ConnectionManager { + + // Skip expensive socket validation for connections idle < 1 second + private static final long VALIDATION_THRESHOLD_NANOS = 1_000_000_000L; + + private final ConcurrentHashMap pools = new ConcurrentHashMap<>(); + private final long maxIdleTimeNanos; + + H1ConnectionManager(long maxIdleTimeNanos) { + this.maxIdleTimeNanos = maxIdleTimeNanos; + } + + /** + * Try to acquire a pooled connection for the route. + * + * @param route the route + * @param maxConnections function to get max connections for route (called lazily) + * @return a valid pooled connection, or null if none available + */ + PooledConnection tryAcquire(Route route, IntFunction poolFactory) { + HostPool hostPool = pools.computeIfAbsent(route, k -> poolFactory.apply(0)); + + PooledConnection pooled; + while ((pooled = hostPool.poll()) != null) { + if (validateConnection(pooled)) { + return pooled; + } + // Connection failed validation - caller should close and release permit + return new PooledConnection(pooled.connection, -1); // -1 signals invalid + } + return null; + } + + /** + * Ensure a pool exists for the route. + */ + void ensurePool(Route route, int maxConnections) { + pools.computeIfAbsent(route, k -> new HostPool(maxConnections)); + } + + /** + * Release a connection back to the pool. + * + * @return true if pooled, false if pool full or closed + */ + boolean release(Route route, HttpConnection connection, boolean poolClosed) { + if (!connection.isActive() || poolClosed) { + return false; + } + + HostPool hostPool = pools.get(route); + if (hostPool == null) { + return false; + } + + try { + return hostPool.offer( + new PooledConnection(connection, System.nanoTime()), + 10, + TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + + /** + * Remove a specific connection from the pool. + */ + void remove(Route route, HttpConnection connection) { + HostPool hostPool = pools.get(route); + if (hostPool != null) { + hostPool.remove(connection); + } + } + + /** + * Clean up idle and unhealthy connections. + * + * @param onRemove callback for each removed connection + * @return total number of connections removed + */ + int cleanupIdle(BiConsumer onRemove) { + int totalRemoved = 0; + for (HostPool pool : pools.values()) { + totalRemoved += pool.removeIdleConnections(maxIdleTimeNanos, onRemove); + } + return totalRemoved; + } + + /** + * Close all pooled connections. + */ + void closeAll(List exceptions, Consumer onClose) { + for (HostPool pool : pools.values()) { + pool.closeAll(exceptions, onClose); + } + pools.clear(); + } + + private boolean validateConnection(PooledConnection pooled) { + long idleNanos = System.nanoTime() - pooled.idleSinceNanos; + if (idleNanos >= maxIdleTimeNanos) { + return false; + } + + if (!pooled.connection.isActive()) { + return false; + } + + if (idleNanos > VALIDATION_THRESHOLD_NANOS) { + return pooled.connection.validateForReuse(); + } + + return true; + } + + /** + * A pooled connection with idle timestamp. + */ + record PooledConnection(HttpConnection connection, long idleSinceNanos) { + boolean isValid() { + return idleSinceNanos >= 0; + } + } + + /** + * Per-route connection pool using blocking deque (LIFO). + */ + static final class HostPool { + private final LinkedBlockingDeque available; + + HostPool(int maxConnections) { + this.available = new LinkedBlockingDeque<>(maxConnections); + } + + PooledConnection poll() { + return available.pollFirst(); + } + + boolean offer(PooledConnection connection, long timeout, TimeUnit unit) throws InterruptedException { + return available.offerFirst(connection, timeout, unit); + } + + void remove(HttpConnection connection) { + available.removeIf(pc -> pc.connection == connection); + } + + int removeIdleConnections(long maxIdleNanos, BiConsumer onRemove) { + int removed = 0; + long now = System.nanoTime(); + Iterator iter = available.iterator(); + while (iter.hasNext()) { + PooledConnection pc = iter.next(); + long idleNanos = now - pc.idleSinceNanos; + boolean unhealthy = !pc.connection.isActive(); + boolean expired = idleNanos > maxIdleNanos; + if (unhealthy || expired) { + CloseReason reason = expired && !unhealthy + ? CloseReason.IDLE_TIMEOUT + : CloseReason.UNEXPECTED_CLOSE; + try { + pc.connection.close(); + } catch (IOException ignored) {} + onRemove.accept(pc.connection, reason); + iter.remove(); + removed++; + } + } + return removed; + } + + void closeAll(List exceptions, Consumer onClose) { + PooledConnection pc; + while ((pc = available.poll()) != null) { + try { + pc.connection.close(); + } catch (IOException e) { + exceptions.add(e); + } + onClose.accept(pc.connection); + } + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java new file mode 100644 index 000000000..5cd69e0a7 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java @@ -0,0 +1,130 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.BiConsumer; +import software.amazon.smithy.java.http.client.h2.H2Connection; + +/** + * Manages HTTP/2 connections for multiplexing. + */ +final class H2ConnectionManager { + private final ConcurrentHashMap> connections = new ConcurrentHashMap<>(); + private final ConcurrentHashMap locks = new ConcurrentHashMap<>(); + private final int streamsPerConnection; + + H2ConnectionManager(int streamPerConnection) { + this.streamsPerConnection = streamPerConnection; + } + + /** + * Find a reusable connection for the route, or null if none available. + * + *

Prefers connections with low stream count to spread load. + */ + H2Connection tryAcquire(Route route) { + List conns = connections.get(route); + if (conns == null) { + return null; + } + + // Prefer low stream count (spreads load) + for (H2Connection conn : conns) { + if (conn.canAcceptMoreStreams() + && conn.getActiveStreamCount() < streamsPerConnection + && conn.validateForReuse()) { + return conn; + } + } + + // Fall back to any available + for (H2Connection conn : conns) { + if (conn.canAcceptMoreStreams() && conn.validateForReuse()) { + return conn; + } + } + + return null; + } + + /** + * Execute an action while holding the lock for this route to make a new connection. + * Prevents duplicate connection creation to the same route. + */ + T newConnection(Route route, ThrowingFunction action) throws E { + synchronized (locks.computeIfAbsent(route, k -> new Object())) { + return action.apply(route); + } + } + + @FunctionalInterface + interface ThrowingFunction { + T apply(Route route) throws E; + } + + /** + * Register a new connection for the route. + */ + void register(Route route, H2Connection conn) { + connections.computeIfAbsent(route, k -> new CopyOnWriteArrayList<>()).add(conn); + } + + /** + * Unregister a connection from the route. + */ + void unregister(Route route, H2Connection conn) { + List conns = connections.get(route); + if (conns != null) { + conns.remove(conn); + } + } + + /** + * Remove dead or exhausted connections for the route. + * + * @param route the route to clean up + * @param onRemove callback for each removed connection (conn, reason) + */ + void cleanupDead(Route route, BiConsumer onRemove) { + List conns = connections.get(route); + if (conns != null) { + conns.removeIf(conn -> { + if (!conn.canAcceptMoreStreams() || !conn.isActive()) { + CloseReason reason = conn.isActive() ? CloseReason.EVICTED : CloseReason.UNEXPECTED_CLOSE; + onRemove.accept(conn, reason); + return true; + } + return false; + }); + } + } + + /** + * Clean up dead connections for all routes. + */ + void cleanupAllDead(BiConsumer onRemove) { + for (Route route : connections.keySet()) { + cleanupDead(route, onRemove); + } + } + + /** + * Close all connections. + * + * @param onClose callback for each connection + */ + void closeAll(BiConsumer onClose) { + for (List conns : connections.values()) { + for (H2Connection conn : conns) { + onClose.accept(conn, CloseReason.POOL_SHUTDOWN); + } + } + connections.clear(); + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnection.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnection.java new file mode 100644 index 000000000..e94602150 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnection.java @@ -0,0 +1,90 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +import java.io.IOException; +import javax.net.ssl.SSLSession; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpExchange; + +/** + * Protocol-agnostic HTTP connection. + */ +public interface HttpConnection extends AutoCloseable { + /** + * Create a new HTTP exchange on this connection. + * + *

For HTTP/1.1: only one exchange at a time. For HTTP/2: multiple concurrent exchanges (multiplexing). + * + * @param request the HTTP request to execute + * @return a new exchange for this request + * @throws IOException if the connection is not in a valid state or network error occurs + * @throws IllegalStateException if connection is closed + */ + HttpExchange newExchange(HttpRequest request) throws IOException; + + /** + * Protocol version of this connection. + * + * @return HTTP version (HTTP/1.1, HTTP/2, etc.) + */ + HttpVersion httpVersion(); + + /** + * Get the destination of the HTTP connection. + * + * @return the request route. + */ + Route route(); + + /** + * Get SSL session if this is a secure connection. + * + *

Provides access to negotiated cipher suite, TLS version, peer certificates, etc. + * + * @return SSLSession, or null if not using TLS + */ + SSLSession sslSession(); + + /** + * Get ALPN negotiated protocol if applicable. + * + * @return "h2", "http/1.1", or null if ALPN not used + */ + String negotiatedProtocol(); + + /** + * Check if connection is still usable for new requests. + * + *

This is meant to be a fast check suitable for frequent calls. For connections retrieved from a pool after + * being idle, use {@link #validateForReuse()} which performs more thorough checks. + * + * @return true if connection can be used for new exchanges + */ + boolean isActive(); + + /** + * Thorough validation to check if a pooled connection can be reused. + * + *

This is more expensive than {@link #isActive()} but catches connections that were closed by the server while + * idle in the pool. Should be called when retrieving a connection that has been idle. + * + *

The default implementation just calls {@link #isActive()}. + * + * @return true if connection is healthy and usable + */ + default boolean validateForReuse() { + return isActive(); + } + + /** + * Close the underlying transport. + * Any active exchanges will be terminated. + */ + @Override + void close() throws IOException; +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java new file mode 100644 index 000000000..7553cfefd --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java @@ -0,0 +1,254 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.time.Duration; +import java.util.List; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocket; +import software.amazon.smithy.java.http.client.ProxyConfiguration; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.h1.H1Connection; +import software.amazon.smithy.java.http.client.h2.H2Connection; + +/** + * Factory for creating HTTP connections. + * + *

Handles connection creation including: + *

    + *
  • DNS resolution with multi-IP failover
  • + *
  • TLS handshake and ALPN negotiation
  • + *
  • Proxy tunneling (HTTP and HTTPS proxies)
  • + *
  • Protocol selection (HTTP/1.1 vs HTTP/2)
  • + *
+ */ +final class HttpConnectionFactory { + + private final Duration connectTimeout; + private final Duration tlsNegotiationTimeout; + private final Duration readTimeout; + private final Duration writeTimeout; + private final SSLContext sslContext; + private final HttpVersionPolicy versionPolicy; + private final DnsResolver dnsResolver; + private final HttpSocketFactory socketFactory; + + HttpConnectionFactory( + Duration connectTimeout, + Duration tlsNegotiationTimeout, + Duration readTimeout, + Duration writeTimeout, + SSLContext sslContext, + HttpVersionPolicy versionPolicy, + DnsResolver dnsResolver, + HttpSocketFactory socketFactory + ) { + this.connectTimeout = connectTimeout; + this.tlsNegotiationTimeout = tlsNegotiationTimeout; + this.readTimeout = readTimeout; + this.writeTimeout = writeTimeout; + this.sslContext = sslContext; + this.versionPolicy = versionPolicy; + this.dnsResolver = dnsResolver; + this.socketFactory = socketFactory; + } + + /** + * Create a new connection to the given route. + * + * @param route the route to connect to + * @return a new HttpConnection + * @throws IOException if connection fails + */ + HttpConnection create(Route route) throws IOException { + if (route.usesProxy()) { + return connectViaProxy(route); + } + + List addresses = dnsResolver.resolve(route.host()); + if (addresses.isEmpty()) { + throw new IOException("DNS resolution failed: no addresses for " + route.host()); + } + + IOException lastException = null; + for (InetAddress address : addresses) { + try { + return connectToAddress(address, route, addresses); + } catch (IOException e) { + lastException = e; + dnsResolver.reportFailure(address); + } + } + + throw new IOException( + "Failed to connect to " + route.host() + " on any resolved IP (" + addresses.size() + " tried)", + lastException); + } + + private HttpConnection connectToAddress(InetAddress address, Route route, List allEndpoints) + throws IOException { + Socket socket = socketFactory.newSocket(route, allEndpoints); + + try { + socket.connect(new InetSocketAddress(address, route.port()), (int) connectTimeout.toMillis()); + } catch (IOException e) { + closeQuietly(socket); + throw e; + } + + if (route.isSecure()) { + socket = performTlsHandshake(socket, route); + } + + return createProtocolConnection(socket, route); + } + + private Socket performTlsHandshake(Socket socket, Route route) throws IOException { + try { + SSLSocket sslSocket = (SSLSocket) sslContext.getSocketFactory() + .createSocket(socket, route.host(), route.port(), true); + + SSLParameters params = sslSocket.getSSLParameters(); + params.setEndpointIdentificationAlgorithm("HTTPS"); + params.setApplicationProtocols(versionPolicy.alpnProtocols()); + sslSocket.setSSLParameters(params); + + int originalTimeout = sslSocket.getSoTimeout(); + sslSocket.setSoTimeout((int) tlsNegotiationTimeout.toMillis()); + try { + sslSocket.startHandshake(); + } finally { + sslSocket.setSoTimeout(originalTimeout); + } + + return sslSocket; + } catch (IOException e) { + closeQuietly(socket); + throw new IOException("TLS handshake failed for " + route.host(), e); + } + } + + private HttpConnection createProtocolConnection(Socket socket, Route route) throws IOException { + String protocol = "http/1.1"; + + if (socket instanceof SSLSocket sslSocket) { + String negotiated = sslSocket.getApplicationProtocol(); + if (negotiated != null && !negotiated.isEmpty()) { + protocol = negotiated; + } + } else if (versionPolicy.usesH2cForCleartext()) { + protocol = "h2c"; + } + + try { + if ("h2".equals(protocol) || "h2c".equals(protocol)) { + return new H2Connection(socket, route, readTimeout, writeTimeout); + } else { + return new H1Connection(socket, route, readTimeout); + } + } catch (IOException e) { + closeQuietly(socket); + throw e; + } + } + + private HttpConnection connectViaProxy(Route route) throws IOException { + ProxyConfiguration proxy = route.proxy(); + + if (proxy.type() == ProxyConfiguration.ProxyType.SOCKS4 + || proxy.type() == ProxyConfiguration.ProxyType.SOCKS5) { + throw new UnsupportedOperationException("SOCKS proxies not yet supported: " + proxy.type()); + } + + List proxyAddresses = dnsResolver.resolve(proxy.hostname()); + if (proxyAddresses.isEmpty()) { + throw new IOException("DNS resolution failed for proxy: " + proxy.hostname()); + } + + IOException lastException = null; + for (InetAddress proxyAddress : proxyAddresses) { + try { + return connectToProxy(proxyAddress, route, proxy, proxyAddresses); + } catch (IOException e) { + lastException = e; + dnsResolver.reportFailure(proxyAddress); + } + } + + throw new IOException( + "Failed to connect to proxy " + proxy.hostname() + " on any resolved IP (" + + proxyAddresses.size() + " tried)", + lastException); + } + + private HttpConnection connectToProxy( + InetAddress proxyAddress, + Route route, + ProxyConfiguration proxy, + List allProxyEndpoints + ) throws IOException { + Socket proxySocket = socketFactory.newSocket(route, allProxyEndpoints); + + try { + proxySocket.connect( + new InetSocketAddress(proxyAddress, proxy.port()), + (int) connectTimeout.toMillis()); + + if (proxy.type() == ProxyConfiguration.ProxyType.HTTPS) { + proxySocket = performTlsHandshakeToProxy(proxySocket, proxy); + } + + if (route.isSecure()) { + H1Connection.establishConnectTunnel(proxySocket, route.host(), route.port(), proxy); + proxySocket = performTlsHandshake(proxySocket, route); + } + + return createProtocolConnection(proxySocket, route); + } catch (IOException e) { + closeQuietly(proxySocket); + throw new IOException( + "Failed to connect to " + route.host() + " via proxy " + + proxy.hostname() + ":" + proxy.port() + " (" + proxyAddress.getHostAddress() + ")", + e); + } + } + + private Socket performTlsHandshakeToProxy(Socket socket, ProxyConfiguration proxy) throws IOException { + try { + SSLSocket sslSocket = (SSLSocket) sslContext.getSocketFactory() + .createSocket(socket, proxy.hostname(), proxy.port(), true); + + SSLParameters params = sslSocket.getSSLParameters(); + params.setEndpointIdentificationAlgorithm("HTTPS"); + sslSocket.setSSLParameters(params); + + int originalTimeout = sslSocket.getSoTimeout(); + sslSocket.setSoTimeout((int) tlsNegotiationTimeout.toMillis()); + try { + sslSocket.startHandshake(); + } finally { + sslSocket.setSoTimeout(originalTimeout); + } + + return sslSocket; + } catch (IOException e) { + throw new IOException("TLS handshake to HTTPS proxy " + proxy.hostname() + " failed", e); + } + } + + private static void closeQuietly(Socket socket) { + try { + socket.close(); + } catch (IOException ignored) { + // ignored + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java new file mode 100644 index 000000000..425641a96 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java @@ -0,0 +1,531 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLContext; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.h2.H2Connection; + +/** + * HTTP connection pool optimized for virtual threads. + * + *

Manages connection lifecycle including: + *

    + *
  • Connection creation with configured SSLContext and version policy
  • + *
  • Connection reuse via pooling (keyed by {@link Route})
  • + *
  • Health monitoring and stale connection cleanup
  • + *
  • DNS resolution with multi-IP failover
  • + *
  • Per-route connection limits with host-specific overrides
  • + *
+ * + *

Thread Safety

+ *

This class is thread-safe for concurrent access. Multiple virtual threads + * can safely acquire and release connections simultaneously. + * + *

Connection Pooling Strategy

+ *

Connections are pooled by {@link Route}, which represents a unique + * destination (scheme + host + port + proxy). Two requests to different paths + * on the same host will share connections: + * + *

{@code
+ * Route route1 = Route.from(URI.create("https://api.example.com/users"));
+ * Route route2 = Route.from(URI.create("https://api.example.com/posts"));
+ * // route1.equals(route2) == true, so connections are shared
+ * }
+ * + *

Per-Route Connection Limits

+ *

You can set different connection limits for different hosts: + * + *

{@code
+ * HttpConnectionPool pool = HttpConnectionPool.builder()
+ *     .maxConnectionsPerRoute(20)  // Default for all routes
+ *     .maxConnectionsForHost("slow-api.example.com", 2)  // Limit slow API
+ *     .maxConnectionsForHost("fast-cdn.example.com", 100)  // Allow more for CDN
+ *     .build();
+ * }
+ * + *

Health Monitoring

+ *

A background virtual thread runs every 30 seconds to remove idle and + * unhealthy connections from the pool. Connections are considered stale if: + *

    + *
  • They've been idle longer than {@code maxIdleTime}
  • + *
  • The underlying socket is closed
  • + *
  • {@link HttpConnection#isActive()} returns false
  • + *
+ * + *

DNS Resolution and Failover

+ *

When creating new connections, the pool resolves hostnames to IP addresses + * using the configured {@link DnsResolver}. If resolution returns multiple IPs, + * the pool attempts to connect to each one until successful: + * + *

{@code
+ * // api.example.com resolves to [203.0.113.1, 203.0.113.2]
+ * // If connection to .1 fails, automatically tries .2
+ * HttpConnection conn = pool.acquire(route);
+ * }
+ * + *

Pool Exhaustion and Backpressure

+ *

When the pool reaches {@code maxTotalConnections}, {@link #acquire(Route)} + * blocks for up to {@code acquireTimeout} (default: 30 seconds) waiting for a + * connection permit to become available. This behavior is consistent for both + * HTTP/1.1 and HTTP/2 connections. + * + *

The blocking wait is on the global connection semaphore, so any connection + * release from any route can unblock waiting callers. With virtual threads, + * this blocking is cheap and provides natural backpressure under load. + * + *

Configure via {@link HttpConnectionPoolBuilder#acquireTimeout(Duration)}: + *

    + *
  • Default (30s): Good backpressure for load spikes, requests queue briefly
  • + *
  • {@link Duration#ZERO}: Fail-fast behavior, immediate failure when exhausted
  • + *
  • Longer timeout: More tolerance for sustained high load
  • + *
+ * + *

Example Usage

+ *
{@code
+ * // Create pool
+ * HttpConnectionPool pool = HttpConnectionPool.builder()
+ *     .maxConnectionsPerRoute(20)
+ *     .maxTotalConnections(200)
+ *     .maxIdleTime(Duration.ofMinutes(2))
+ *     .sslContext(customSSLContext)
+ *     .httpVersionPolicy(HttpVersionPolicy.AUTOMATIC)
+ *     .build();
+ *
+ * // Acquire connection
+ * Route route = Route.from(URI.create("https://api.example.com/users"));
+ * HttpConnection conn = pool.acquire(route);
+ *
+ * try {
+ *     // Use connection
+ *     HttpExchange exchange = conn.newExchange(request);
+ *     // ...
+ * } finally {
+ *     // Return to pool for reuse
+ *     pool.release(conn, route);
+ * }
+ *
+ * // Cleanup
+ * pool.close();
+ * }
+ * + * @see Route + * @see HttpConnection + * @see HttpVersionPolicy + */ +public final class HttpConnectionPool implements ConnectionPool { + // Target streams per connection before creating a new one. + // Lower = more connections, better throughput under contention + // Higher = fewer connections, better multiplexing efficiency + private static final int STREAMS_PER_CONNECTION = 50; + + private final int defaultMaxConnectionsPerRoute; + private final Map perHostLimits; + private final int maxTotalConnections; + private final long maxIdleTimeNanos; // Cached to avoid Duration.toNanos() in hot path + private final long acquireTimeoutMs; // Timeout for acquiring a connection when pool is exhausted + private final HttpVersionPolicy versionPolicy; + private final HttpConnectionFactory connectionFactory; + + // HTTP/1.1 connection manager (handles pooling) + private final H1ConnectionManager h1Manager; + + // HTTP/2 connection manager (handles multiplexing) + private final H2ConnectionManager h2Manager = new H2ConnectionManager(STREAMS_PER_CONNECTION); + + // Semaphore to limit total connections - better contention than AtomicInteger CAS loop + private final Semaphore connectionPermits; + + // Cleanup thread + private final Thread cleanupThread; + private volatile boolean closed = false; + + // Listeners for pool lifecycle events + private final List listeners; + + HttpConnectionPool(HttpConnectionPoolBuilder builder) { + this.defaultMaxConnectionsPerRoute = builder.maxConnectionsPerRoute; + this.perHostLimits = Map.copyOf(builder.perHostLimits); + this.maxTotalConnections = builder.maxTotalConnections; + this.maxIdleTimeNanos = builder.maxIdleTime.toNanos(); + this.acquireTimeoutMs = builder.acquireTimeout.toMillis(); + this.versionPolicy = builder.versionPolicy; + DnsResolver dnsResolver = builder.dnsResolver != null ? builder.dnsResolver : DnsResolver.system(); + SSLContext sslContext = builder.sslContext; + + if (sslContext == null) { + try { + sslContext = SSLContext.getDefault(); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("No default SSLContext available", e); + } + } + + this.connectionFactory = new HttpConnectionFactory( + builder.connectTimeout, + builder.tlsNegotiationTimeout, + builder.readTimeout, + builder.writeTimeout, + sslContext, + builder.versionPolicy, + dnsResolver, + builder.socketFactory); + + this.h1Manager = new H1ConnectionManager(maxIdleTimeNanos); + this.connectionPermits = new Semaphore(builder.maxTotalConnections, false); + this.listeners = List.copyOf(builder.listeners); + this.cleanupThread = Thread.ofVirtual().name("http-pool-cleanup").start(this::cleanupIdleConnections); + } + + /** + * Create a new builder for HttpConnectionPool. + * + * @return a new builder instance + */ + public static HttpConnectionPoolBuilder builder() { + return new HttpConnectionPoolBuilder(); + } + + @Override + public HttpConnection acquire(Route route) throws IOException { + if (closed) { + throw new IllegalStateException("Connection pool is closed"); + } else if ((route.isSecure() && versionPolicy != HttpVersionPolicy.ENFORCE_HTTP_1_1) + || (!route.isSecure() && versionPolicy.usesH2cForCleartext())) { + return acquireH2(route); + } else { + return acquireH1(route); + } + } + + private HttpConnection acquireH1(Route route) throws IOException { + int maxConns = getMaxConnectionsForRoute(route); + + // Quick check: try to reuse a pooled connection + H1ConnectionManager.PooledConnection pooled = h1Manager.tryAcquire( + route, + ignored -> new H1ConnectionManager.HostPool(maxConns)); + + if (pooled != null) { + if (pooled.isValid()) { + notifyAcquire(pooled.connection(), true); + return pooled.connection(); + } + // Connection failed validation: it's unhealthy or stale + closeConnection(pooled.connection()); + connectionPermits.release(); + } + + // No valid pooled connection available, so block on global capacity with timeout. + acquirePermit(); + + // Create new HTTP/1.1 connection + try { + HttpConnection conn = connectionFactory.create(route); + notifyConnected(conn); + notifyAcquire(conn, false); + return conn; + } catch (IOException | RuntimeException e) { + connectionPermits.release(); + throw e; + } + } + + private HttpConnection acquireH2(Route route) throws IOException { + // Fast path: find an existing H2 connection with capacity + H2Connection reusable = h2Manager.tryAcquire(route); + if (reusable != null) { + notifyAcquire(reusable, true); + return reusable; + } + + // Slow path: need to create a new H2 connection. + return h2Manager.newConnection(route, r -> { + // Double-check: another thread might have created while we waited. + H2Connection rechecked = h2Manager.tryAcquire(r); + if (rechecked != null) { + notifyAcquire(rechecked, true); + return rechecked; + } + + // Clean up dead or unhealthy connections + h2Manager.cleanupDead(r, this::closeAndReleasePermit); + + // Create new H2 connection - block on global capacity with timeout + acquirePermit(); + + HttpConnection conn = null; + try { + conn = connectionFactory.create(r); + notifyConnected(conn); + if (conn instanceof H2Connection newH2conn) { + h2Manager.register(r, newH2conn); + notifyAcquire(newH2conn, false); + return newH2conn; + } else { + // ALPN negotiated HTTP/1.1 instead of H2. + h1Manager.ensurePool(r, getMaxConnectionsForRoute(r)); + notifyAcquire(conn, false); + return conn; + } + } catch (IOException | RuntimeException e) { + if (conn != null) { + closeConnection(conn); + } + connectionPermits.release(); + throw e; + } + }); + } + + /** + * Acquire a connection permit, blocking up to acquireTimeout. + */ + private void acquirePermit() throws IOException { + try { + if (!connectionPermits.tryAcquire(acquireTimeoutMs, TimeUnit.MILLISECONDS)) { + throw new IOException("Connection pool exhausted: " + maxTotalConnections + + " connections in use (timed out after " + acquireTimeoutMs + "ms)"); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Interrupted while waiting for connection", e); + } + } + + @Override + public void release(HttpConnection connection) { + Objects.requireNonNull(connection, "connection cannot be null"); + Route route = connection.route(); + + notifyReturn(connection); + + // H2 connections stay active for multiplexing - don't pool them + if (connection instanceof H2Connection h2conn) { + if (!connection.isActive() || closed) { + h2Manager.unregister(route, h2conn); + closeAndReleasePermit(connection, CloseReason.UNEXPECTED_CLOSE); + } + return; + } + + // H1 connection handling + if (!h1Manager.release(route, connection, closed)) { + closeAndReleasePermit(connection, CloseReason.POOL_FULL); + } + } + + @Override + public void evict(HttpConnection connection, boolean isError) { + Objects.requireNonNull(connection, "connection cannot be null"); + Route route = connection.route(); + + if (connection instanceof H2Connection h2conn) { + h2Manager.unregister(route, h2conn); + } else { + h1Manager.remove(route, connection); + } + + closeAndReleasePermit(connection, isError ? CloseReason.ERRORED : CloseReason.EVICTED); + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + + closed = true; + cleanupThread.interrupt(); + + List exceptions = new ArrayList<>(); + + // Close active H2 connections + h2Manager.closeAll((conn, reason) -> { + try { + conn.close(); + } catch (IOException e) { + exceptions.add(e); + } + notifyClosed(conn, CloseReason.POOL_SHUTDOWN); + }); + + // Close pooled H1 connections + h1Manager.closeAll(exceptions, conn -> notifyClosed(conn, CloseReason.POOL_SHUTDOWN)); + + if (!exceptions.isEmpty()) { + IOException e = new IOException("Errors closing connections"); + exceptions.forEach(e::addSuppressed); + throw e; + } + } + + @Override + public void shutdown(Duration gracePeriod) throws IOException { + Objects.requireNonNull(gracePeriod, "gracePeriod cannot be null"); + + if (closed) { + return; + } + + closed = true; // Stop new acquires + cleanupThread.interrupt(); + + // Wait for connections to be closed (permits represent physical connections, not streams). + // For HTTP/2, permits are released when the connection closes, not when streams finish. + Instant deadline = Instant.now().plus(gracePeriod); + while (connectionPermits.availablePermits() < maxTotalConnections + && Instant.now().isBefore(deadline)) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + + // Force close remaining + close(); + } + + /** + * Get max connections for a specific route. + * + *

Checks host-specific limits configured via + * {@link HttpConnectionPoolBuilder#maxConnectionsForHost(String, int)}, falling back to + * the default limit if no specific limit is configured. + * + *

Host matching is case-insensitive and supports: + *

    + *
  • Hostname only: "api.example.com" (matches default ports 80/443)
  • + *
  • Hostname with port: "api.example.com:8080" (matches only port 8080)
  • + *
+ * + * @param route the route to get limit for + * @return maximum connections for this route + */ + private int getMaxConnectionsForRoute(Route route) { + // common case: no custom per-host limits configured + if (perHostLimits.isEmpty()) { + return defaultMaxConnectionsPerRoute; + } + + String host = route.host(); + + // Check with port first (more specific) + if (route.port() != 80 && route.port() != 443) { + String hostWithPort = host + ":" + route.port(); + Integer limit = perHostLimits.get(hostWithPort); + if (limit != null) { + return limit; + } + } + + // Check without port (less specific) + Integer limit = perHostLimits.get(host); + if (limit != null) { + return limit; + } + + // Use default + return defaultMaxConnectionsPerRoute; + } + + /** + * Close a connection, ignoring any IOException. + * + * @param connection the connection to close + */ + private void closeConnection(HttpConnection connection) { + try { + connection.close(); + } catch (IOException ignored) { + // ignored + } + } + + /** + * Close a connection, notify listeners, and release its permit. + */ + private void closeAndReleasePermit(HttpConnection connection, CloseReason reason) { + closeConnection(connection); + notifyClosed(connection, reason); + connectionPermits.release(); + } + + private void notifyConnected(HttpConnection connection) { + for (ConnectionPoolListener listener : listeners) { + listener.onConnected(connection); + } + } + + private void notifyAcquire(HttpConnection connection, boolean reused) { + for (ConnectionPoolListener listener : listeners) { + listener.onAcquire(connection, reused); + } + } + + private void notifyReturn(HttpConnection connection) { + for (ConnectionPoolListener listener : listeners) { + listener.onReturn(connection); + } + } + + private void notifyClosed(HttpConnection connection, CloseReason reason) { + for (ConnectionPoolListener listener : listeners) { + listener.onClosed(connection, reason); + } + } + + /** + * Background cleanup task that runs every 30 seconds. + * + *

For HTTP/1.1 connections, removes connections that: + *

    + *
  • Have been idle longer than {@code maxIdleTime}
  • + *
  • Are no longer active ({@link HttpConnection#isActive()} is false)
  • + *
+ * + *

For HTTP/2 connections, removes connections that: + *

    + *
  • Are no longer active or can't accept more streams
  • + *
+ * + *

Note: {@code maxIdleTime} currently only applies to HTTP/1.1 connections. + * HTTP/2 connections remain open until they become unhealthy. + * + *

Runs on a virtual thread, so blocking is cheap. + */ + private void cleanupIdleConnections() { + while (!closed) { + try { + Thread.sleep(Duration.ofSeconds(30)); + + // Clean up HTTP/1.1 connections + int removed = h1Manager.cleanupIdle(this::notifyClosed); + if (removed > 0) { + connectionPermits.release(removed); + } + + // Clean up unhealthy HTTP/2 connections + h2Manager.cleanupAllDead(this::closeAndReleasePermit); + + } catch (InterruptedException e) { + break; + } + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java new file mode 100644 index 000000000..b4577c0c3 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java @@ -0,0 +1,401 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import javax.net.ssl.SSLContext; +import software.amazon.smithy.java.http.client.dns.DnsResolver; + +/** + * Builder for HttpConnectionPool. + */ +public final class HttpConnectionPoolBuilder { + int maxConnectionsPerRoute = 20; + final Map perHostLimits = new HashMap<>(); + int maxTotalConnections = 200; + Duration maxIdleTime = Duration.ofMinutes(2); + Duration acquireTimeout = Duration.ofSeconds(30); + Duration connectTimeout = Duration.ofSeconds(10); + Duration tlsNegotiationTimeout = Duration.ofSeconds(10); + Duration readTimeout = Duration.ofSeconds(30); + Duration writeTimeout = Duration.ofSeconds(30); + SSLContext sslContext; + HttpVersionPolicy versionPolicy = HttpVersionPolicy.AUTOMATIC; + DnsResolver dnsResolver; + HttpSocketFactory socketFactory = HttpSocketFactory::defaultSocketFactory; + final List listeners = new LinkedList<>(); + + /** + * Set default maximum connections per route (default: 20). + * + *

This is the default limit for all routes unless overridden via + * {@link #maxConnectionsForHost(String, int)}. + * + *

Each route (unique scheme+host+port+proxy combination) gets its own + * connection pool with this capacity. + * + *

Note: Per-route limits only apply to HTTP/1.1 connections. + * HTTP/2 connections use multiplexing to handle many concurrent requests over + * fewer connections, so only {@link #maxTotalConnections(int)} applies to them. + * + * @param max maximum connections per route, must be positive + * @return this builder + * @throws IllegalArgumentException if max is not positive + */ + public HttpConnectionPoolBuilder maxConnectionsPerRoute(int max) { + if (max <= 0) { + throw new IllegalArgumentException("maxConnectionsPerRoute must be positive: " + max); + } + this.maxConnectionsPerRoute = max; + return this; + } + + /** + * Set maximum connections for a specific host (overrides default). + * + *

Host format examples: + *

    + *
  • {@code "api.example.com"} - applies to default port (80/443)
  • + *
  • {@code "api.example.com:8080"} - applies only to port 8080
  • + *
+ * + *

Example usage: + *

{@code
+     * builder
+     *     .maxConnectionsPerRoute(20)  // Default for all routes
+     *     .maxConnectionsForHost("slow-api.example.com", 2)  // Limit slow API
+     *     .maxConnectionsForHost("fast-cdn.example.com", 100)  // Allow more for CDN
+     * }
+ * + *

Host matching is case-insensitive. If a port-specific limit is set, + * it takes precedence over the host-only limit. + * + *

Note: Per-host limits only apply to HTTP/1.1 connections and are + * always capped by {@link #maxTotalConnections(int)}. If a per-host limit exceeds + * {@code maxTotalConnections}, the global limit takes precedence. + * + * @param host the hostname (with optional port), case-insensitive + * @param max maximum connections for this specific host, must be positive + * @return this builder + * @throws IllegalArgumentException if host is null/empty or max is not positive + */ + public HttpConnectionPoolBuilder maxConnectionsForHost(String host, int max) { + if (host == null || host.isEmpty()) { + throw new IllegalArgumentException("host must not be null or empty"); + } + if (max <= 0) { + throw new IllegalArgumentException("max must be positive: " + max); + } + perHostLimits.put(host.toLowerCase(), max); + return this; + } + + /** + * Set maximum total connections across all routes (default: 200). + * + *

This is a global limit across all routes to prevent unbounded + * connection growth. When this limit is reached, {@link HttpConnectionPool#acquire(Route)} + * will throw IOException. + * + *

Must be at least as large as {@code maxConnectionsPerRoute}. + * + * @param max maximum total connections, must be positive + * @return this builder + * @throws IllegalArgumentException if max is not positive + */ + public HttpConnectionPoolBuilder maxTotalConnections(int max) { + if (max <= 0) { + throw new IllegalArgumentException("maxTotalConnections must be positive: " + max); + } + this.maxTotalConnections = max; + return this; + } + + /** + * Set maximum idle time before connections are closed (default: 2 minutes). + * + *

Connections that have been idle (in the pool) longer than this duration + * are closed by the background cleanup thread. + * + *

Note: This setting currently only applies to HTTP/1.1 connections. + * HTTP/2 connections use multiplexing and remain open until they become unhealthy + * (e.g., server closes the connection or GOAWAY is received). + * + *

Set lower for short-lived applications or high-churn workloads. + * Set higher for long-running applications with steady traffic. + * + * @param duration maximum idle time, must be positive + * @return this builder + * @throws IllegalArgumentException if duration is null, negative, or zero + */ + public HttpConnectionPoolBuilder maxIdleTime(Duration duration) { + if (duration == null || duration.isNegative() || duration.isZero()) { + throw new IllegalArgumentException("maxIdleTime must be positive: " + duration); + } + this.maxIdleTime = duration; + return this; + } + + /** + * Set acquire timeout for waiting when pool is exhausted (default: 30 seconds). + * + *

When {@link #maxTotalConnections(int)} is reached, {@link HttpConnectionPool#acquire(Route)} + * will block for up to this duration waiting for a connection to become available. + * If no connection becomes available within this time, an {@link IOException} is thrown. + * + *

This timeout applies uniformly to both HTTP/1.1 and HTTP/2 connections. + * With virtual threads, blocking is cheap, so a longer timeout (30s default) + * provides good backpressure behavior under load spikes. + * + *

Set to {@link Duration#ZERO} for fail-fast behavior (immediate failure + * when pool is exhausted, no waiting). + * + * @param timeout acquire timeout duration, must be non-negative + * @return this builder + * @throws IllegalArgumentException if timeout is null or negative + */ + public HttpConnectionPoolBuilder acquireTimeout(Duration timeout) { + if (timeout == null || timeout.isNegative()) { + throw new IllegalArgumentException("acquireTimeout must be non-negative: " + timeout); + } + this.acquireTimeout = timeout; + return this; + } + + /** + * Set connection timeout (default: 10 seconds). + * + *

This is the maximum time to wait for TCP connection establishment. + * If the connection doesn't complete within this time, the attempt fails + * and the next resolved IP (if any) is tried. + * + * @param timeout connection timeout duration, must be non-negative + * @return this builder + * @throws IllegalArgumentException if timeout is null or negative + */ + public HttpConnectionPoolBuilder connectTimeout(Duration timeout) { + if (timeout == null || timeout.isNegative()) { + throw new IllegalArgumentException("connectTimeout must be non-negative: " + timeout); + } + this.connectTimeout = timeout; + return this; + } + + /** + * Set TLS negotiation timeout (default: 10 seconds). + * + *

This is the maximum time to wait for TLS handshake completion. + * If the handshake doesn't complete within this time, the connection fails. + * + *

Separate from {@link #connectTimeout(Duration)} because TLS handshake + * happens after TCP connection is established. + * + * @param timeout TLS negotiation timeout, must be non-negative + * @return this builder + * @throws IllegalArgumentException if timeout is null or negative + */ + public HttpConnectionPoolBuilder tlsNegotiationTimeout(Duration timeout) { + if (timeout == null || timeout.isNegative()) { + throw new IllegalArgumentException("tlsNegotiationTimeout must be non-negative: " + timeout); + } + this.tlsNegotiationTimeout = timeout; + return this; + } + + /** + * Set read timeout for waiting on response data (default: 30 seconds). + * + *

This timeout applies to: + *

    + *
  • Waiting for response headers after sending request
  • + *
  • Waiting for response body data chunks
  • + *
+ * + *

If no data is received within this duration, a + * {@link java.net.SocketTimeoutException} is thrown. + * + * @param timeout read timeout duration, must be non-negative + * @return this builder + * @throws IllegalArgumentException if timeout is null or negative + */ + public HttpConnectionPoolBuilder readTimeout(Duration timeout) { + if (timeout == null || timeout.isNegative()) { + throw new IllegalArgumentException("readTimeout must be non-negative: " + timeout); + } + this.readTimeout = timeout; + return this; + } + + /** + * Set write timeout for sending request data (default: 30 seconds). + * + *

This timeout applies to waiting for flow control window space + * when sending request body data. If flow control prevents sending + * within this duration, a {@link java.net.SocketTimeoutException} is thrown. + * + * @param timeout write timeout duration, must be non-negative + * @return this builder + * @throws IllegalArgumentException if timeout is null or negative + */ + public HttpConnectionPoolBuilder writeTimeout(Duration timeout) { + if (timeout == null || timeout.isNegative()) { + throw new IllegalArgumentException("writeTimeout must be non-negative: " + timeout); + } + this.writeTimeout = timeout; + return this; + } + + /** + * Set SSL context for HTTPS connections (default: {@link SSLContext#getDefault()}). + * + *

Configure a custom SSLContext for: + *

    + *
  • Custom CA bundles (via TrustManager)
  • + *
  • Client certificate authentication/mTLS (via KeyManager)
  • + *
  • Custom TLS settings (via SSLParameters)
  • + *
+ * + *

Example with custom CA: + *

{@code
+     * KeyStore trustStore = KeyStore.getInstance("PKCS12");
+     * trustStore.load(...);
+     *
+     * TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+     *     TrustManagerFactory.getDefaultAlgorithm()
+     * );
+     * tmf.init(trustStore);
+     *
+     * SSLContext ctx = SSLContext.getInstance("TLS");
+     * ctx.init(null, tmf.getTrustManagers(), null);
+     *
+     * builder.sslContext(ctx);
+     * }
+ * + * @param context the SSL context to use for HTTPS connections + * @return this builder + */ + public HttpConnectionPoolBuilder sslContext(SSLContext context) { + this.sslContext = context; + return this; + } + + /** + * Set HTTP version policy to control which protocol versions are negotiated via ALPN (default: AUTOMATIC). + * + * @param policy the version policy to use + * @return this builder + * @throws IllegalArgumentException if policy is null + */ + public HttpConnectionPoolBuilder httpVersionPolicy(HttpVersionPolicy policy) { + Objects.requireNonNull(policy, "httpVersionPolicy cannot be null"); + this.versionPolicy = policy; + return this; + } + + /** + * Set DNS resolver for hostname resolution (default: system resolver with 1-minute cache). + * + * @param resolver the DNS resolver to use + * @return this builder + * @throws IllegalArgumentException if resolver is null + */ + public HttpConnectionPoolBuilder dnsResolver(DnsResolver resolver) { + Objects.requireNonNull(resolver, "dnsResolver must not be null"); + this.dnsResolver = resolver; + return this; + } + + /** + * Set socket factory (default: creates socket with TCP_NODELAY=true, SO_KEEPALIVE=true). + * + *

The factory creates and configures sockets before they are connected. + * + *

Example: + *

{@code
+     * builder.socketFactory((route, endpoints) -> {
+     *     Socket socket = new Socket();
+     *     socket.setTcpNoDelay(true);
+     *     socket.setKeepAlive(true);
+     *     if (route.host().endsWith(".internal")) {
+     *         socket.setSendBufferSize(256 * 1024);
+     *     }
+     *     return socket;
+     * });
+     * }
+ * + * @param socketFactory creates and configures sockets before connection + * @return this builder + * @throws NullPointerException if socketFactory is null + * @see HttpSocketFactory + */ + public HttpConnectionPoolBuilder socketFactory(HttpSocketFactory socketFactory) { + this.socketFactory = Objects.requireNonNull(socketFactory, "socketFactory"); + return this; + } + + /** + * Add a listener for connection pool lifecycle events. + * + *

Listeners are notified of connection creation, acquisition, release, and eviction events. Multiple + * listeners can be added and are called in order. Listeners are called synchronously, so calls should be fast. + * + * @param listener the listener to add + * @return this builder + * @throws NullPointerException if listener is null + * @see ConnectionPoolListener + */ + public HttpConnectionPoolBuilder addListener(ConnectionPoolListener listener) { + listeners.add(Objects.requireNonNull(listener, "listener")); + return this; + } + + /** + * Add a listener at the front of the listener list. + * + *

This listener will be called before any previously added listeners. + * Useful for adding wrapper/decorator listeners that should see events first. + * + * @param listener the listener to add + * @return this builder + * @throws NullPointerException if listener is null + * @see #addListener(ConnectionPoolListener) + */ + public HttpConnectionPoolBuilder addListenerFirst(ConnectionPoolListener listener) { + listeners.addFirst(Objects.requireNonNull(listener, "listener")); + return this; + } + + /** + * Build the connection pool. + * + * @return a new connection pool instance + * @throws IllegalStateException if the configuration is invalid + */ + public HttpConnectionPool build() { + if (sslContext == null) { + try { + sslContext = SSLContext.getDefault(); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("Failed to get default SSLContext", e); + } + } + + if (maxTotalConnections < maxConnectionsPerRoute) { + throw new IllegalStateException( + "maxTotalConnections (" + maxTotalConnections + ") must be >= " + + "maxConnectionsPerRoute (" + maxConnectionsPerRoute + ")"); + } + + return new HttpConnectionPool(this); + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java new file mode 100644 index 000000000..9dc20e5ba --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java @@ -0,0 +1,70 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +import java.io.UncheckedIOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; +import java.util.List; + +/** + * Factory for creating sockets used by the connection pool, allowing for customizing socket options. + * + *

The socket returned should be unconnected. The pool will call {@link Socket#connect} after receiving the socket + * from this factory. + * + *

Example

+ * {@snippet : + * HttpConnectionPool pool = HttpConnectionPool.builder() + * .socketFactory((route, endpoints) -> { + * Socket socket = new Socket(); + * socket.setTcpNoDelay(true); + * socket.setKeepAlive(true); + * if (route.host().endsWith(".internal")) { + * socket.setSendBufferSize(256 * 1024); + * } + * return socket; + * }) + * .build(); + * } + * + * @see HttpConnectionPoolBuilder#socketFactory(HttpSocketFactory) + */ +@FunctionalInterface +public interface HttpSocketFactory { + /** + * Create a new unconnected socket for the given route. + * + * @param route the target route (host, port, secure flag) + * @param endpoints the resolved IP addresses for the route's host, in preference order + * @return a new unconnected socket + */ + Socket newSocket(Route route, List endpoints); + + /** + * Default factory used to create sockets. + * + *

Creates sockets with TCP_NODELAY=true, SO_KEEPALIVE=true, and 64KB send/receive buffers. + * + * @param route the target route (unused in default implementation) + * @param endpoints the resolved endpoints (unused in default implementation) + * @return the created socket + */ + static Socket defaultSocketFactory(Route route, List endpoints) { + try { + Socket socket = new Socket(); + socket.setTcpNoDelay(true); + socket.setKeepAlive(true); + // Larger buffers for high throughput + socket.setSendBufferSize(64 * 1024); + socket.setReceiveBufferSize(64 * 1024); + return socket; + } catch (SocketException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpVersionPolicy.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpVersionPolicy.java new file mode 100644 index 000000000..9abb7ab2c --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpVersionPolicy.java @@ -0,0 +1,47 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +/** + * HTTP protocol version policy for connection negotiation. + */ +public enum HttpVersionPolicy { + /** HTTP/1.1 only. For TLS, negotiates only "http/1.1" via ALPN. */ + ENFORCE_HTTP_1_1, + + /** HTTP/2 over TLS only. Negotiates only "h2" via ALPN. Fails if server doesn't support. */ + ENFORCE_HTTP_2, + + /** Prefer HTTP/2, fall back to HTTP/1.1. Uses HTTP/1.1 for cleartext. Recommended default. */ + AUTOMATIC, + + /** HTTP/2 over cleartext (h2c) using prior knowledge. */ + H2C_PRIOR_KNOWLEDGE; + + /** + * Get ALPN protocol strings for this policy. + * + *

Only applicable for TLS connections. For cleartext, use {@link #usesH2cForCleartext()}. + * + * @return array of ALPN protocol strings in preference order + */ + public String[] alpnProtocols() { + return switch (this) { + case ENFORCE_HTTP_1_1 -> new String[] {"http/1.1"}; + case ENFORCE_HTTP_2, H2C_PRIOR_KNOWLEDGE -> new String[] {"h2"}; + case AUTOMATIC -> new String[] {"h2", "http/1.1"}; + }; + } + + /** + * Check if this policy uses h2c (HTTP/2 cleartext) for non-TLS connections. + * + * @return true if h2c prior knowledge should be used for cleartext + */ + public boolean usesH2cForCleartext() { + return this == H2C_PRIOR_KNOWLEDGE; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/Route.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/Route.java new file mode 100644 index 000000000..f79765317 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/Route.java @@ -0,0 +1,283 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +import java.net.URI; +import java.util.Objects; +import software.amazon.smithy.java.http.client.ProxyConfiguration; + +/** + * A route represents a unique destination for HTTP connections. + * + *

Connections to the same route can be pooled and reused. Two routes are equal if they connect to the same + * destination via the same path (including proxy configuration). + * + *

Important: Routes are compared by value, not identity. Two Route instances with the same scheme, host, + * port, and proxy configuration are considered equal and will share connections. + * + *

Example: + * {@snippet : + * Route route1 = Route.from(URI.create("https://api.example.com/users")); + * Route route2 = Route.from(URI.create("https://api.example.com/posts")); + * assert route1.equals(route2); + * + * Route route3 = Route.from(URI.create("https://other.example.com/data")); + * assert !route1.equals(route3); + * } + * + *

Proxy routing: + * {@snippet : + * ProxyConfiguration proxy = ProxyConfiguration.builder() + * .proxyUri("http://proxy.corp.com:8080") + * .type(ProxyType.HTTP) + * .build(); + * + * Route directRoute = Route.from(uri); + * Route proxiedRoute = Route.from(uri, proxy); + * + * // Different routes - proxied connections can't be shared with direct + * assert !directRoute.equals(proxiedRoute); + * } + */ +public final class Route { + private final String scheme; + private final String host; + private final int port; + private final ProxyConfiguration proxy; + private final int cachedHashCode; + + /** + * Create a new Route. + * + * @param scheme Scheme: "http" or "https". + * @param host Target hostname (case-insensitive, normalized to lowercase). + * @param port Target port (always explicit, never -1). + * @param proxy Optional proxy configuration. Null if connecting directly without proxy. + */ + public Route(String scheme, String host, int port, ProxyConfiguration proxy) { + Objects.requireNonNull(scheme, "scheme cannot be null"); + Objects.requireNonNull(host, "host cannot be null"); + + if (!scheme.equals("http") && !scheme.equals("https")) { + throw new IllegalArgumentException("Invalid scheme: " + scheme + " (must be 'http' or 'https')"); + } + + if (port <= 0 || port > 65535) { + throw new IllegalArgumentException("Invalid port: " + port + " (must be 1-65535)"); + } + + if (host.isBlank()) { + throw new IllegalArgumentException("host cannot be blank"); + } + + // Normalize host to lowercase for consistent equality + this.scheme = scheme; + this.host = host.toLowerCase(); + this.port = port; + this.proxy = proxy; + + // Cache hashCode for fast map lookups in connection pool + // Manual computation avoids Objects.hash() varargs array allocation + int h = this.scheme.hashCode(); + h = 31 * h + this.host.hashCode(); + h = 31 * h + this.port; + h = 31 * h + (this.proxy != null ? this.proxy.hashCode() : 0); + this.cachedHashCode = h; + } + + /** + * @return the scheme ("http" or "https") + */ + public String scheme() { + return scheme; + } + + /** + * @return the target hostname (normalized to lowercase) + */ + public String host() { + return host; + } + + /** + * @return the target port + */ + public int port() { + return port; + } + + /** + * @return the proxy configuration, or null if direct connection + */ + public ProxyConfiguration proxy() { + return proxy; + } + + /** + * Check if this is a secure (HTTPS) route. + * + * @return true if scheme is "https" + */ + public boolean isSecure() { + return "https".equals(scheme); + } + + /** + * Check if this route goes through a proxy. + * + * @return true if proxy configuration is present + */ + public boolean usesProxy() { + return proxy != null; + } + + /** + * Get the effective connection target (where the TCP socket connects). + * + *

If using a proxy, returns the proxy's host:port. + * Otherwise, returns the target host:port. + * + *

Note: For HTTP proxies with HTTPS targets, the socket connects to + * the proxy, then a CONNECT tunnel is established to the target. + * + * @return connection target in "host:port" format + */ + public String connectionTarget() { + if (usesProxy()) { + return proxy.hostname() + ":" + proxy.port(); + } + return host + ":" + port; + } + + /** + * Get the tunnel target for CONNECT requests. + * Only relevant when using a proxy with HTTPS. + * + * @return tunnel target in "host:port" format + */ + public String tunnelTarget() { + return host + ":" + port; + } + + /** + * Create a Route from a URI without proxy. + * + *

The URI's path, query, and fragment are ignored. + * Only scheme, host, and port are used. + * + * @param uri the URI to extract route from + * @return a Route for direct connection + * @throws IllegalArgumentException if URI is invalid + */ + public static Route from(URI uri) { + return from(uri, null); + } + + /** + * Create a Route from a URI with optional proxy configuration. + * + *

The URI's path, query, and fragment are ignored. + * Only scheme, host, and port are used. + * + * @param uri the URI to extract route from + * @param proxy optional proxy configuration (null for direct connection) + * @return a Route for the given URI and proxy + * @throws IllegalArgumentException if URI is invalid + */ + public static Route from(URI uri, ProxyConfiguration proxy) { + String scheme = uri.getScheme(); + if (scheme == null) { + throw new IllegalArgumentException("URI must have a scheme: " + uri); + } + + String host = uri.getHost(); + if (host == null) { + throw new IllegalArgumentException("URI must have a host: " + uri); + } + + int port = uri.getPort(); + if (port == -1) { + // Use scheme default + port = "https".equals(scheme) ? 443 : 80; + } + + // Check if this host should bypass proxy + if (proxy != null && proxy.shouldBypass(host)) { + proxy = null; + } + + return new Route(scheme, host, port, proxy); + } + + /** + * Create a Route for direct connection (no proxy). + * + * @param scheme "http" or "https" + * @param host target hostname + * @param port target port + * @return a Route for direct connection + */ + public static Route direct(String scheme, String host, int port) { + return new Route(scheme, host, port, null); + } + + /** + * Create a Route through a proxy. + * + * @param scheme "http" or "https" + * @param host target hostname + * @param port target port + * @param proxy proxy configuration + * @return a Route for proxied connection + */ + public static Route viaProxy(String scheme, String host, int port, ProxyConfiguration proxy) { + Objects.requireNonNull(proxy, "proxy cannot be null (use direct() for no proxy)"); + return new Route(scheme, host, port, proxy); + } + + /** + * Create a new Route with a different proxy configuration. + * + * @param proxy new proxy configuration (null for direct connection) + * @return new Route with updated proxy + */ + public Route withProxy(ProxyConfiguration proxy) { + return Objects.equals(this.proxy, proxy) ? this : new Route(scheme, host, port, proxy); + } + + /** + * Create a new Route without proxy (direct connection). + * + * @return new Route with no proxy + */ + public Route withoutProxy() { + return proxy == null ? this : new Route(scheme, host, port, null); + } + + @Override + public int hashCode() { + return cachedHashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Route other)) { + return false; + } + return port == other.port + && scheme.equals(other.scheme) + && host.equals(other.host) + && Objects.equals(proxy, other.proxy); + } + + @Override + public String toString() { + return "Route[scheme=" + scheme + ", host=" + host + ", port=" + port + ", proxy=" + proxy + "]"; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/DnsResolver.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/DnsResolver.java new file mode 100644 index 000000000..391622064 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/DnsResolver.java @@ -0,0 +1,107 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.dns; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.List; +import java.util.Map; + +/** + * A blocking DNS resolver used to resolve hostnames to IP addresses. + * + *

Thread-safe: All implementations must be safe for concurrent use. + */ +public interface DnsResolver { + // Design note: we return all addresses and not just one because it allows for algorithms like happy eyeballs to + // race connections across IPs. + + /** + * Resolves a hostname to IP addresses. + * + *

Returns all available addresses in preference order, typically with IPv6 + * addresses before IPv4 as determined by the system's address selection policy + * (RFC 6724). + * + *

Implementations may: + *

    + *
  • Rotate addresses across calls for load distribution
  • + *
  • Cache results with appropriate TTL
  • + *
  • Exclude recently failed addresses
  • + *
+ * + *

This method may block for DNS lookup. + * + * @param hostname the hostname to resolve (e.g., "api.example.com") + * @return unmodifiable list of resolved IP addresses, never null or empty + * @throws IOException if DNS resolution fails and no cached addresses are available + */ + List resolve(String hostname) throws IOException; + + /** + * Reports that a connection attempt to an address failed. + * + *

Implementations may use this to temporarily deprioritize or exclude the + * address from future results until it likely recovers. + * + *

Default: No-op. Stateless resolvers ignore failure reports. + * + * @param address the IP address that failed to connect + */ + default void reportFailure(InetAddress address) {} + + /** + * Purges cached entries for a specific hostname. + * + *

Forces a fresh DNS lookup on the next {@link #resolve} call for this hostname. + * + *

Default: No-op. Stateless resolvers have no cache. + * + * @param hostname the hostname to purge from cache + */ + default void purgeCache(String hostname) {} + + /** + * Purges all cached entries. + * + *

Forces fresh DNS lookups for all hostnames. + * + *

Default: No-op. Stateless resolvers have no cache. + */ + default void purgeCache() {} + + /** + * Creates a DNS resolver using the JVM's default resolution. + * + *

Delegates to {@link InetAddress#getAllByName(String)}, which respects + * JVM DNS cache settings configured via security properties: + *

    + *
  • {@code networkaddress.cache.ttl} - seconds to cache successful lookups (default: 30)
  • + *
  • {@code networkaddress.cache.negative.ttl} - seconds to cache failures (default: 10)
  • + *
+ * + *

This resolver is stateless and does not track failures or perform rotation. + * + * @return system DNS resolver singleton + */ + static DnsResolver system() { + return SystemDnsResolver.INSTANCE; + } + + /** + * Creates a DNS resolver with static hostname mappings. + * + *

Returns pre-configured addresses without performing DNS queries. + * Useful for testing and local development. + * + * @param mappings hostname to address list mappings + * @return static DNS resolver + * @throws NullPointerException if mappings is null + */ + static DnsResolver staticMapping(Map> mappings) { + return new StaticDnsResolver(mappings); + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/StaticDnsResolver.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/StaticDnsResolver.java new file mode 100644 index 000000000..76dabcbc6 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/StaticDnsResolver.java @@ -0,0 +1,77 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.dns; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * DNS resolver with static hostname-to-IP mappings. + * + *

This resolver returns pre-configured addresses for known hostnames without + * performing any network DNS queries. It is useful for: + *

    + *
  • Testing without real DNS infrastructure
  • + *
  • Local development with custom hostname mappings
  • + *
  • Overriding specific hostnames while delegating others
  • + *
+ * + *

Example Usage

+ * + * {@snippet : + * var resolver = new StaticDnsResolver(Map.of( + * "api.example.com", new InetAddress[] { + * InetAddress.getByName("192.168.1.100"), + * InetAddress.getByName("192.168.1.101") + * }, + * "localhost", new InetAddress[] { + * InetAddress.getLoopbackAddress() + * } + * )); + * } + */ +record StaticDnsResolver(Map> mappings) implements DnsResolver { + /** + * Creates a static resolver with the given hostname mappings. + * + *

The mappings are defensively copied to prevent external modification. + * + * @param mappings hostname to address list mappings; empty lists are permitted + * but will cause {@link #resolve} to throw for that hostname + */ + StaticDnsResolver(Map> mappings) { + Map> copy = new HashMap<>(mappings.size()); + for (Map.Entry> entry : mappings.entrySet()) { + List value = entry.getValue(); + if (value != null && !value.isEmpty()) { + copy.put(entry.getKey().toLowerCase(Locale.ROOT), List.copyOf(value)); + } + } + this.mappings = Map.copyOf(copy); + } + + /** + * Resolves a hostname to its configured addresses. + * + *

Returns the pre-configured address list for the hostname. + * + * @param hostname the hostname to resolve + * @return the configured addresses for this hostname, never empty + * @throws IOException if no mapping exists for the hostname or the mapping is empty + */ + @Override + public List resolve(String hostname) throws IOException { + List addresses = mappings.get(hostname.toLowerCase(Locale.ROOT)); + if (addresses == null) { + throw new IOException("No static mapping defined for hostname: " + hostname); + } + return addresses; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/SystemDnsResolver.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/SystemDnsResolver.java new file mode 100644 index 000000000..40c253daf --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/SystemDnsResolver.java @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.dns; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; + +/** + * DNS resolver using the JVM's default resolution mechanism. + * + *

This resolver delegates to {@link InetAddress#getAllByName(String)}, which typically uses the operating system's + * DNS resolution. The JVM maintains its own DNS cache with configurable TTLs via security properties: + *

    + *
  • {@code networkaddress.cache.ttl} - seconds to cache successful lookups
  • + *
  • {@code networkaddress.cache.negative.ttl} - seconds to cache failed lookups
  • + *
+ * + *

This resolver is stateless and does not perform any caching beyond what the JVM provides. It returns all + * addresses from DNS resolution, preserving the order returned by the underlying resolver. + */ +final class SystemDnsResolver implements DnsResolver { + + static final SystemDnsResolver INSTANCE = new SystemDnsResolver(); + + private SystemDnsResolver() {} + + @Override + public List resolve(String hostname) throws IOException { + try { + InetAddress[] addresses = InetAddress.getAllByName(hostname); + if (addresses.length == 0) { + throw new IOException("DNS resolution returned no addresses for: " + hostname); + } + return List.of(addresses); + } catch (UnknownHostException e) { + throw new IOException("Failed to resolve hostname: " + hostname, e); + } + } + + @Override + public String toString() { + return "SystemDnsResolver"; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java new file mode 100644 index 000000000..008587f18 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java @@ -0,0 +1,288 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.ModifiableHttpHeaders; +import software.amazon.smithy.java.http.client.UnsyncBufferedInputStream; + +/** + * InputStream that reads HTTP/1.1 chunked transfer encoding format (RFC 7230 Section 4.1). + * + *

ChunkedInputStream intentionally doesn't close the delegate stream because it's a view over one response on a + * potentially long-lived socket. The socket lifecycle is managed by H1Connection, which is managed by the pool. + */ +final class ChunkedInputStream extends InputStream { + private static final long MAX_CHUNK_SIZE = readMaxChunkSize(); + private static final long DEFAULT_MAX_CHUNK_SIZE = 1024 * 1024; // 1 MB + private static final int MAX_LINE_LENGTH = 8192; + + private final UnsyncBufferedInputStream delegate; + private long chunkRemaining = -1; // -1 means need to read chunk size + private boolean eof; + private boolean closed; + private final byte[] lineBuffer = new byte[MAX_LINE_LENGTH]; + private HttpHeaders trailers; // Trailer headers parsed from final chunk (RFC 7230 Section 4.1.2) + + ChunkedInputStream(UnsyncBufferedInputStream delegate) { + this.delegate = delegate; + } + + private static long readMaxChunkSize() { + String property = System.getProperty("SMITHY_HTTP_CLIENT_MAX_CHUNK_SIZE"); + if (property == null) { + return DEFAULT_MAX_CHUNK_SIZE; + } + try { + long size = Long.parseLong(property); + if (size <= 0) { + throw new IllegalArgumentException("SMITHY_HTTP_CLIENT_MAX_CHUNK_SIZE must be positive: " + size); + } + return size; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid SMITHY_HTTP_CLIENT_MAX_CHUNK_SIZE: " + property, e); + } + } + + @Override + public int read() throws IOException { + if (closed || eof) { + return -1; + } + + // Need to read next chunk? + if (chunkRemaining == -1 || chunkRemaining == 0) { + if (!readNextChunk()) { + return -1; // EOF + } + } + + // Read one byte from current chunk + int b = delegate.read(); + if (b != -1) { + chunkRemaining--; + } else { + // Unexpected EOF + throw new IOException("Unexpected end of stream in chunked encoding"); + } + + return b; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (closed || eof) { + return -1; + } else if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + // Need to read next chunk? + if (chunkRemaining == -1 || chunkRemaining == 0) { + if (!readNextChunk()) { + return -1; // EOF + } + } + + // Read at most chunkRemaining bytes + int toRead = (int) Math.min(len, chunkRemaining); + int n = delegate.read(b, off, toRead); + + if (n > 0) { + chunkRemaining -= n; + } else if (n == -1) { + throw new IOException("Unexpected end of stream in chunked encoding"); + } + + return n; + } + + @Override + public long skip(long n) throws IOException { + if (closed || eof || n <= 0) { + return 0; + } + + byte[] buffer = new byte[8192]; + long remaining = n; + + while (remaining > 0) { + int toRead = (int) Math.min(buffer.length, remaining); + int bytesRead = read(buffer, 0, toRead); + if (bytesRead == -1) { + break; + } + remaining -= bytesRead; + } + + return n - remaining; + } + + @Override + public int available() throws IOException { + if (closed || eof) { + return 0; + } + + if (chunkRemaining > 0) { + // We know up to chunkRemaining bytes remain in this chunk; cap by delegate.available(). + int available = delegate.available(); + return (int) Math.min(available, chunkRemaining); + } + + return 0; + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + closed = true; + + // Drain remaining chunks to allow connection reuse + if (!eof) { + byte[] drain = new byte[8192]; + while (read(drain) != -1) { + // Discard + } + } + + // Don't close delegate - connection may be reused + } + + /** + * Read the next chunk header and update state. + * + * @return true if there's more data, false if final chunk (size 0) + * @throws IOException if chunk format is invalid + */ + private boolean readNextChunk() throws IOException { + // If we just finished a chunk, consume trailing CRLF + if (chunkRemaining == 0) { + readCRLF(); + } + + // Read chunk size line directly into buffer + int lineLen = delegate.readLine(lineBuffer, MAX_LINE_LENGTH); + + long chunkSize = getChunkSize(lineLen); + + if (chunkSize > MAX_CHUNK_SIZE) { + throw new IOException("Chunk size " + chunkSize + " exceeds maximum allowed size of " + MAX_CHUNK_SIZE); + } + + if (chunkSize == 0) { + // Final chunk - read optional trailers + readTrailers(); + eof = true; + chunkRemaining = 0; + return false; + } + + chunkRemaining = chunkSize; + return true; + } + + private long getChunkSize(int lineLen) throws IOException { + if (lineLen <= 0) { + throw new IOException("Empty chunk size line"); + } + + // Find end of hex size (stop at semicolon for chunk extensions, or end of line) + int sizeEnd = lineLen; + for (int i = 0; i < lineLen; i++) { + byte b = lineBuffer[i]; + if (b == ';' || b == ' ') { + sizeEnd = i; + break; + } + } + + if (sizeEnd == 0) { + throw new IOException("Missing chunk size"); + } + + // Parse hex directly from bytes + long chunkSize = parseHex(lineBuffer, 0, sizeEnd); + if (chunkSize < 0) { + throw new IOException("Negative chunk size: " + chunkSize); + } + return chunkSize; + } + + private static long parseHex(byte[] buf, int start, int end) throws IOException { + long value = 0; + for (int i = start; i < end; i++) { + byte b = buf[i]; + int digit; + if (b >= '0' && b <= '9') { + digit = b - '0'; + } else if (b >= 'a' && b <= 'f') { + digit = 10 + (b - 'a'); + } else if (b >= 'A' && b <= 'F') { + digit = 10 + (b - 'A'); + } else { + throw new IOException("Invalid hex character in chunk size: " + (char) b); + } + value = (value << 4) | digit; + } + return value; + } + + /** + * Read and parse trailer headers after final chunk (RFC 7230 Section 4.1.2). + * + *

Trailers are formatted like HTTP headers and are read until a blank line. + * Parsed trailers are stored and can be retrieved via {@link #getTrailers()}. + */ + private void readTrailers() throws IOException { + ModifiableHttpHeaders parsedTrailers = HttpHeaders.ofModifiable(); + int len; + while ((len = delegate.readLine(lineBuffer, MAX_LINE_LENGTH)) > 0) { + String name = HttpUtils.parseHeaderLine(lineBuffer, len, parsedTrailers); + if (name == null) { + throw new IOException("Invalid trailer line: " + + new String(lineBuffer, 0, len, StandardCharsets.US_ASCII)); + } + } + + // Only store if we actually got trailers + if (!parsedTrailers.isEmpty()) { + this.trailers = parsedTrailers; + } + } + + /** + * Get trailer headers parsed from the chunked stream. + * + *

Trailers are only available after the stream has been fully read (EOF reached). + * Before EOF, this method returns null. + * + * @return trailer headers, or null if no trailers were received or stream not fully read + */ + HttpHeaders getTrailers() { + return trailers; + } + + private void readCRLF() throws IOException { + int cr = delegate.read(); + int lf = delegate.read(); + if (cr == -1 || lf == -1) { + throw new IOException("Unexpected end of stream: expected CRLF after chunk data"); + } + if (cr != '\r' || lf != '\n') { + throw new IOException(String.format("Expected CRLF after chunk data, got 0x%02X 0x%02X", cr, lf)); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStream.java new file mode 100644 index 000000000..e19b3c0dd --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStream.java @@ -0,0 +1,187 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +/** + * OutputStream that writes HTTP/1.1 chunked transfer encoding format (RFC 7230 Section 4.1). + * + *

This stream does not close the delegate on close, allowing the underlying socket to be reused + * for subsequent HTTP/1.1 requests. The socket lifecycle is managed by {@link H1Connection}. + */ +final class ChunkedOutputStream extends OutputStream { + private final OutputStream delegate; + private final byte[] buffer; + private int bufferPos = 0; + private boolean closed = false; + + // Default chunk size: 8KB + private static final int DEFAULT_CHUNK_SIZE = 8192; + + /** + * Create a ChunkedOutputStream with default chunk size (8KB). + */ + ChunkedOutputStream(OutputStream delegate) { + this(delegate, DEFAULT_CHUNK_SIZE); + } + + /** + * Create a ChunkedOutputStream with specified chunk size. + * + * @param delegate underlying stream to write chunks to + * @param chunkSize maximum size of each chunk in bytes (must be > 0) + */ + ChunkedOutputStream(OutputStream delegate, int chunkSize) { + if (delegate == null) { + throw new NullPointerException("delegate"); + } + if (chunkSize <= 0) { + throw new IllegalArgumentException("chunkSize must be positive: " + chunkSize); + } + + this.delegate = delegate; + this.buffer = new byte[chunkSize]; + } + + @Override + public void write(int b) throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + + buffer[bufferPos++] = (byte) b; + + if (bufferPos >= buffer.length) { + flushChunk(); + } + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + + if (b == null) { + throw new NullPointerException(); + } + if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return; + } + + int remaining = len; + int offset = off; + + while (remaining > 0) { + int available = buffer.length - bufferPos; + int toCopy = Math.min(remaining, available); + + System.arraycopy(b, offset, buffer, bufferPos, toCopy); + bufferPos += toCopy; + offset += toCopy; + remaining -= toCopy; + + if (bufferPos >= buffer.length) { + flushChunk(); + } + } + } + + @Override + public void flush() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + + // Flush any buffered data as a chunk + if (bufferPos > 0) { + flushChunk(); + } + + // Flush underlying stream + delegate.flush(); + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + + closed = true; + + // Flush any remaining buffered data + if (bufferPos > 0) { + flushChunk(); + } + + // Write final 0-sized chunk + writeFinalChunk(); + // Flush underlying stream, and don't close delegate on failure since the connection may be reused + delegate.flush(); + } + + /** + * Flush the current buffer as a chunk. + */ + private void flushChunk() throws IOException { + if (bufferPos == 0) { + return; + } + + writeChunk(buffer, 0, bufferPos); + bufferPos = 0; + } + + /** + * Write a chunk with the given data. + * + *

Format: + * {size-in-hex}\r\n + * {data}\r\n + */ + private void writeChunk(byte[] data, int off, int len) throws IOException { + // Write chunk size in hexadecimal + String hexSize = Integer.toHexString(len); + delegate.write(hexSize.getBytes(StandardCharsets.US_ASCII)); + delegate.write('\r'); + delegate.write('\n'); + + // Write chunk data + delegate.write(data, off, len); + + // Write trailing CRLF + delegate.write('\r'); + delegate.write('\n'); + } + + /** + * Write the final 0-sized chunk. + * + *

Format: + * 0\r\n + * \r\n + */ + private void writeFinalChunk() throws IOException { + // Write "0\r\n\r\n" + delegate.write('0'); + delegate.write('\r'); + delegate.write('\n'); + delegate.write('\r'); + delegate.write('\n'); + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/FailingOutputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/FailingOutputStream.java new file mode 100644 index 000000000..ab5b2a3fa --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/FailingOutputStream.java @@ -0,0 +1,36 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * OutputStream that immediately throws a pre-existing exception on any operation. + * Used when Expect: 100-continue fails before body transmission. + */ +class FailingOutputStream extends OutputStream { + private final IOException exception; + + FailingOutputStream(IOException exception) { + this.exception = exception; + } + + @Override + public void write(int b) throws IOException { + throw exception; + } + + @Override + public void flush() throws IOException { + throw exception; + } + + @Override + public void close() throws IOException { + throw exception; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Connection.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Connection.java new file mode 100644 index 000000000..0b30a4ccf --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Connection.java @@ -0,0 +1,396 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Base64; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpExchange; +import software.amazon.smithy.java.http.client.ProxyConfiguration; +import software.amazon.smithy.java.http.client.UnsyncBufferedInputStream; +import software.amazon.smithy.java.http.client.UnsyncBufferedOutputStream; +import software.amazon.smithy.java.http.client.connection.HttpConnection; +import software.amazon.smithy.java.http.client.connection.Route; +import software.amazon.smithy.java.logging.InternalLogger; + +/** + * HTTP/1.1 connection implementation. + * + *

Manages a single TCP socket for HTTP/1.1 communication. HTTP/1.1 allows only one request/response exchange at + * a time (no multiplexing like HTTP/2). + * + *

Connection Reuse

+ *

Supports HTTP/1.1 persistent connections (keep-alive). After each exchange, the connection can be returned to + * the pool for reuse if: + *

    + *
  • The server sent "Connection: keep-alive" (or didn't send "Connection: close")
  • + *
  • The response body was fully read
  • + *
  • No errors occurred during the exchange
  • + *
+ * + *

Thread Safety

+ *

This class is thread-safe for {@link #newExchange(HttpRequest)} - only one exchange can be active at a time. + * Concurrent calls to {@code newExchange()} will fail with an exception if another exchange is already active. + * + *

Proxy Support

+ *

If created through an HTTP proxy with CONNECT tunnel (for HTTPS), the underlying socket is already connected + * through the tunnel. All proxy handshaking happens during connection establishment, not in this class. + */ +public final class H1Connection implements HttpConnection { + /** + * The buffer used for parsing response start-line and each header line. This means that the path and query + * string can't exceed 8KB, and no one header can exceed 8KB (though we do impose a limit of 512 headers to guard + * against malformed responses). This per/line limit should be more than enough for well-formed response parsing. + */ + static final int RESPONSE_LINE_BUFFER_SIZE = 8192; + + private static final InternalLogger LOGGER = InternalLogger.getLogger(H1Connection.class); + + private final Socket socket; + private final UnsyncBufferedInputStream socketIn; + private final UnsyncBufferedOutputStream socketOut; + private final Route route; + private final byte[] lineBuffer; // Reused across exchanges for header parsing + + // HTTP/1.1: only one exchange at a time + private final AtomicBoolean inUse = new AtomicBoolean(false); + private volatile boolean keepAlive = true; + private volatile boolean active = true; + + /** + * Create an HTTP/1.1 connection from a connected socket with timeout. + * + *

The socket must already be connected (and if using HTTPS, TLS handshake + * must be complete). + * + * @param socket the connected socket + * @param route Connection route + * @param readTimeout timeout for read operations (applied via SO_TIMEOUT) + * @throws IOException if socket streams cannot be obtained + */ + public H1Connection(Socket socket, Route route, Duration readTimeout) throws IOException { + this.socket = socket; + this.socketIn = new UnsyncBufferedInputStream(socket.getInputStream(), 8192); + this.socketOut = new UnsyncBufferedOutputStream(socket.getOutputStream(), 8192); + this.route = route; + this.lineBuffer = new byte[RESPONSE_LINE_BUFFER_SIZE]; + + // Set socket read timeout - throws SocketTimeoutException on timeout + if (readTimeout != null && !readTimeout.isZero()) { + socket.setSoTimeout((int) readTimeout.toMillis()); + } + } + + @Override + public HttpExchange newExchange(HttpRequest request) throws IOException { + if (!active) { + throw new IOException("Connection is closed"); + } else if (!inUse.compareAndSet(false, true)) { + throw new IOException("Connection already in use (concurrent exchange attempted)"); + } + + try { + return new H1Exchange(this, request, route, lineBuffer); + } catch (IOException e) { + // Failed to create exchange, release + releaseExchange(); + throw e; + } + } + + @Override + public HttpVersion httpVersion() { + return HttpVersion.HTTP_1_1; + } + + @Override + public boolean isActive() { + // Fast path: just check volatile flags (no syscalls) + // Full socket health check happens in validateForReuse() when connection + // is retrieved from pool after being idle + return active && keepAlive; + } + + @Override + public boolean validateForReuse() { + if (!active || !keepAlive) { + return false; + } + + // Check socket state (syscalls, but only when validating for reuse) + if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) { + LOGGER.debug("Connection to {} is closed or half-closed", route); + markInactive(); + return false; + } + + // Check if server closed connection while idle (sent FIN) + try { + if (socketIn.available() > 0) { + LOGGER.debug("Unexpected data available on idle connection to {}", route); + markInactive(); + return false; + } + } catch (IOException e) { + LOGGER.debug("IOException checking socket state for {}: {}", route, e.getMessage()); + markInactive(); + return false; + } + + return true; + } + + @Override + public Route route() { + return route; + } + + @Override + public SSLSession sslSession() { + if (socket instanceof SSLSocket sslSocket) { + return sslSocket.getSession(); + } + return null; + } + + @Override + public String negotiatedProtocol() { + if (socket instanceof SSLSocket sslSocket) { + String protocol = sslSocket.getApplicationProtocol(); + return (protocol != null && !protocol.isEmpty()) ? protocol : null; + } + return null; + } + + @Override + public void close() throws IOException { + active = false; + socket.close(); + } + + /** + * Set socket read timeout. + * + * @param timeoutMs timeout in milliseconds + * @throws IOException if setting timeout fails + */ + void setSocketTimeout(int timeoutMs) throws IOException { + socket.setSoTimeout(timeoutMs); + } + + /** + * Get current socket read timeout. + * + * @return timeout in milliseconds + * @throws IOException if getting timeout fails + */ + int getSocketTimeout() throws IOException { + return socket.getSoTimeout(); + } + + /** + * Release the exchange, allowing the connection to be reused. + * + *

Called by {@link H1Exchange} when the exchange completes. + */ + void releaseExchange() { + inUse.set(false); + } + + /** + * Set whether this connection supports keep-alive. + * + *

Called by {@link H1Exchange} after parsing response headers. + * If the server sends "Connection: close", keep-alive is disabled and + * the connection will not be reused. + * + * @param keepAlive true if connection can be reused + */ + void setKeepAlive(boolean keepAlive) { + this.keepAlive = keepAlive; + } + + /** + * Check if this connection supports keep-alive. + * + * @return true if connection can be reused after current exchange + */ + boolean isKeepAlive() { + return keepAlive; + } + + /** + * Get the input stream for reading responses. + * + * @return socket input stream + */ + UnsyncBufferedInputStream getInputStream() { + return socketIn; + } + + /** + * Get the output stream for writing requests. + * + * @return socket output stream + */ + UnsyncBufferedOutputStream getOutputStream() { + return socketOut; + } + + /** + * Mark this connection as inactive due to an error. + * + *

Called by {@link H1Exchange} when errors occur during I/O. + */ + void markInactive() { + LOGGER.debug("Marking connection inactive to {}", route); + this.active = false; + } + + /** + * Establish an HTTP CONNECT tunnel through a proxy. + * + *

This is a static factory method that performs the proxy handshake + * and returns a connected socket ready for use. The CONNECT tunnel is + * only needed for HTTPS through an HTTP proxy. + * + *

For HTTP through a proxy, no tunnel is needed - requests are sent + * with absolute URIs directly to the proxy. + * + *

CONNECT Protocol

+ *
+     * Client → Proxy:
+     *   CONNECT api.example.com:443 HTTP/1.1
+     *   Host: api.example.com:443
+     *   Proxy-Authorization: Basic dXNlcjpwYXNz  (if auth required)
+     *
+     * Proxy → Client:
+     *   HTTP/1.1 200 Connection Established
+     * 
+ * + *

After receiving 200, the socket is connected through the tunnel + * and TLS handshake can proceed as if connecting directly. + * + * @param proxySocket socket connected to proxy server + * @param targetHost target host for CONNECT request + * @param targetPort target port for CONNECT request + * @param proxy proxy configuration (for authentication) + * @throws IOException if CONNECT tunnel establishment fails + */ + public static void establishConnectTunnel( + Socket proxySocket, + String targetHost, + int targetPort, + ProxyConfiguration proxy + ) throws IOException { + + try { + OutputStream out = proxySocket.getOutputStream(); + InputStream in = proxySocket.getInputStream(); + + // Build CONNECT request + StringBuilder request = new StringBuilder(); + request.append("CONNECT ") + .append(targetHost) + .append(":") + .append(targetPort) + .append(" HTTP/1.1\r\n"); + request.append("Host: ") + .append(targetHost) + .append(":") + .append(targetPort) + .append("\r\n"); + + // Add proxy authentication if configured + if (proxy.requiresAuth()) { + String credentials = proxy.username() + ":" + proxy.password(); + String encoded = Base64.getEncoder() + .encodeToString( + credentials.getBytes(StandardCharsets.UTF_8)); + request.append("Proxy-Authorization: Basic ").append(encoded).append("\r\n"); + } + + // Keep proxy connection alive for reuse + request.append("Proxy-Connection: Keep-Alive\r\n"); + request.append("\r\n"); + + // Send CONNECT request + out.write(request.toString().getBytes(StandardCharsets.US_ASCII)); + out.flush(); + + // Read response status line + BufferedReader reader = new BufferedReader( + new InputStreamReader(in, StandardCharsets.US_ASCII)); + String statusLine = reader.readLine(); + + if (statusLine == null) { + throw new IOException("Proxy closed connection during CONNECT handshake"); + } + + // Parse status line: "HTTP/1.1 200 Connection Established" + String[] parts = statusLine.split("\\s+", 3); + if (parts.length < 2) { + throw new IOException("Invalid proxy response: " + statusLine); + } + + int statusCode; + try { + statusCode = Integer.parseInt(parts[1]); + } catch (NumberFormatException e) { + throw new IOException("Invalid status code in proxy response: " + statusLine); + } + + // Read and discard response headers until empty line + String line; + while ((line = reader.readLine()) != null && !line.isEmpty()) { + // Skip header lines + } + + // Check status code + if (statusCode == 200) { + // Tunnel established successfully + return; + } else if (statusCode == 407) { + // Proxy authentication required + throw new IOException("Proxy authentication required (407). Check proxy credentials."); + } else if (statusCode >= 400 && statusCode < 500) { + // Client error + throw new IOException( + "Proxy rejected CONNECT request: " + statusCode + " " + + (parts.length > 2 ? parts[2] : "")); + } else if (statusCode >= 500) { + // Server error + throw new IOException( + "Proxy server error during CONNECT: " + statusCode + " " + + (parts.length > 2 ? parts[2] : "")); + } else { + // Unexpected status code + throw new IOException("Unexpected proxy response: " + statusLine); + } + + } catch (IOException e) { + // Close socket on any error + try { + proxySocket.close(); + } catch (IOException ignored) {} + throw new IOException( + "Failed to establish CONNECT tunnel to " + targetHost + ":" + targetPort + + " through proxy", + e); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java new file mode 100644 index 000000000..5efbfc825 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java @@ -0,0 +1,568 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import software.amazon.smithy.java.http.api.HeaderUtils; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.api.ModifiableHttpHeaders; +import software.amazon.smithy.java.http.client.BoundedInputStream; +import software.amazon.smithy.java.http.client.DelegatedClosingInputStream; +import software.amazon.smithy.java.http.client.HttpExchange; +import software.amazon.smithy.java.http.client.NonClosingOutputStream; +import software.amazon.smithy.java.http.client.UnsyncBufferedInputStream; +import software.amazon.smithy.java.http.client.UnsyncBufferedOutputStream; +import software.amazon.smithy.java.http.client.connection.Route; + +/** + * HTTP/1.1 exchange implementation, handling a single request/response over a connection. + * + *

Request/Response Flow

+ *

HTTP/1.1 is a sequential protocol: the request must be fully sent before the response can be read. This class + * enforces this ordering: + *

    + *
  1. Request line and headers are written on construction
  2. + *
  3. Request body is written via {@link #requestBody()}
  4. + *
  5. Response is read via {@link #responseStatusCode()}, {@link #responseHeaders()}, {@link #responseBody()}
  6. + *
+ * + *

Expect: 100-continue

+ *

When the request includes {@code Expect: 100-continue}, the client: + *

    + *
  1. Sends headers and waits for 100 Continue response
  2. + *
  3. If server sends 100, proceeds to send body
  4. + *
  5. If server sends 4xx/5xx, skips body transmission
  6. + *
  7. If server doesn't respond within timeout, proceeds anyway
  8. + *
+ * + *

Proxy Support

+ *

When used through a non-tunneled HTTP proxy, requests are formatted with absolute URIs instead of relative paths: + *

    + *
  • Direct/tunneled: {@code GET /users HTTP/1.1}
  • + *
  • HTTP proxy: {@code GET http://api.example.com/users HTTP/1.1}
  • + *
+ * + *

Transfer Encoding

+ *

Supports both chunked transfer encoding and fixed Content-Length for request and response bodies. + */ +public final class H1Exchange implements HttpExchange { + + private static final int MAX_RESPONSE_HEADER_COUNT = 512; + private static final long DEFAULT_CONTINUE_TIMEOUT_MS = 1000; // 1 second + + // Pre-allocated byte arrays for constant HTTP strings + private static final byte[] HTTP_1_1_CRLF = " HTTP/1.1\r\n".getBytes(StandardCharsets.US_ASCII); + private static final byte[] CRLF = "\r\n".getBytes(StandardCharsets.US_ASCII); + private static final byte[] COLON_SPACE = ": ".getBytes(StandardCharsets.US_ASCII); + private static final byte[] HOST_HEADER = "Host: ".getBytes(StandardCharsets.US_ASCII); + + private final H1Connection connection; + private final HttpRequest request; + private final Route route; + private final byte[] responseLineBuffer; // Reused buffer for header parsing + + private OutputStream requestOut; + private InputStream responseIn; + private ChunkedInputStream chunkedResponseIn; // Reference for trailer access + private HttpHeaders responseHeaders; + private HttpVersion responseVersion; + private int statusCode = -1; + private boolean requestWritten = false; + private boolean expectContinueHandled = false; + private boolean closed; + + /** + * Create a new HTTP/1.1 exchange. + * + *

Immediately writes request line and headers to the connection. + * + * @param connection the HTTP/1.1 connection to use + * @param request the HTTP request to send + * @param route the route this connection is for (needed for proxy formatting) + * @param lineBuffer reusable buffer for reading response header lines + * @throws IOException if writing request line or headers fails + */ + H1Exchange(H1Connection connection, HttpRequest request, Route route, byte[] lineBuffer) throws IOException { + this.connection = connection; + this.request = request; + this.route = route; + this.responseLineBuffer = lineBuffer; + + // Write request line and headers directly to output buffer + UnsyncBufferedOutputStream out = connection.getOutputStream(); + writeRequestLine(out); + writeHeaders(out, request.headers()); + out.flush(); + } + + @Override + public HttpRequest request() { + return request; + } + + @Override + public OutputStream requestBody() { + if (requestOut == null) { + OutputStream socketOut = connection.getOutputStream(); + var headers = request.headers(); + + // Handle Expect: 100-continue before creating output stream + String expectHeader = headers.firstValue("expect"); + if (expectHeader != null && expectHeader.equalsIgnoreCase("100-continue")) { + try { + handleExpectContinue(); + } catch (IOException e) { + // Wrap exception for later throwing when writing + requestOut = new FailingOutputStream(e); + return requestOut; + } + } + + String transferEncoding = headers.firstValue("transfer-encoding"); + if ("chunked".equalsIgnoreCase(transferEncoding)) { + // RFC 9110 Section 6.3: Content-Length MUST NOT be sent with Transfer-Encoding + if (headers.firstValue("content-length") != null) { + throw new IllegalArgumentException( + "Request cannot have both Content-Length and Transfer-Encoding headers"); + } + requestOut = new ChunkedOutputStream(socketOut); + } else { + requestOut = new NonClosingOutputStream(socketOut); + } + } + return requestOut; + } + + @Override + public InputStream responseBody() throws IOException { + if (responseIn == null) { + ensureRequestComplete(); + if (statusCode == -1) { + parseStatusLineAndHeaders(); + } + // For HTTP/1.1, request is already complete, so close exchange when response closes + responseIn = new DelegatedClosingInputStream(createResponseStream(), this::close); + } + return responseIn; + } + + @Override + public boolean supportsBidirectionalStreaming() { + return false; + } + + @Override + public HttpHeaders responseHeaders() throws IOException { + if (responseHeaders == null) { + ensureRequestComplete(); + parseStatusLineAndHeaders(); + } + return responseHeaders; + } + + @Override + public int responseStatusCode() throws IOException { + if (statusCode == -1) { + ensureRequestComplete(); + parseStatusLineAndHeaders(); + } + return statusCode; + } + + @Override + public HttpVersion responseVersion() throws IOException { + if (responseVersion == null) { + ensureRequestComplete(); + parseStatusLineAndHeaders(); + } + return responseVersion; + } + + @Override + public void close() throws IOException { + if (!closed) { + closed = true; + if (responseIn != null) { + responseIn.close(); + } + if (requestOut != null) { + requestOut.close(); + } + connection.releaseExchange(); + } + } + + /** + * Get trailer headers from chunked transfer encoding response. + * + *

Trailers are only available for chunked responses after the entire + * response body has been read. For non-chunked responses, this returns null. + * + * @return trailer headers, or null if no trailers were received + */ + @Override + public HttpHeaders responseTrailerHeaders() { + // Trailers are only available from chunked responses + if (chunkedResponseIn != null) { + return chunkedResponseIn.getTrailers(); + } + return null; + } + + /** + * Handle Expect: 100-continue negotiation. + * + *

Waits up to N seconds for 100 Continue response: + *

    + *
  • 100 Continue → proceed with body
  • + *
  • 417 Expectation Failed → throw exception
  • + *
  • Other response → parse as final response, skip body
  • + *
  • Timeout → proceed with body anyway
  • + *
+ * + * @throws IOException if error response received or I/O fails + */ + private void handleExpectContinue() throws IOException { + if (expectContinueHandled) { + return; + } + expectContinueHandled = true; + + UnsyncBufferedInputStream in = connection.getInputStream(); + + // Set socket timeout for 100-continue response + int originalTimeout; + try { + originalTimeout = connection.getSocketTimeout(); + connection.setSocketTimeout((int) DEFAULT_CONTINUE_TIMEOUT_MS); + } catch (IOException e) { + // Can't set timeout - proceed without waiting + return; + } + + try { + // Try to read 100 Continue response + int lineLen = readLine(in); + + if (lineLen <= 0) { + // Timeout waiting for response - proceed with body + return; + } + + int code = parseStatusLine(responseLineBuffer, lineLen); + + if (code == 100) { + // 100 Continue received - read and discard headers, proceed with body + while (readLine(in) > 0) { + // Skip header lines until empty line + } + } else if (code == 417) { + // 417 Expectation Failed - server rejected Expect + throw new IOException("Server rejected Expect: 100-continue with 417 Expectation Failed"); + } else { + // Server sent final response without 100 Continue + // Parse as final response, must not send body + parseStatusAndHeaders(code, in); + requestWritten = true; // Skip body transmission + throw new IOException("Server sent final response " + code + + " before request body; body must not be written"); + } + } catch (SocketTimeoutException e) { + // Timeout waiting for 100 Continue - proceed with body anyway + // Some servers don't support 100-continue and just ignore it + return; + } finally { + // Restore original timeout + try { + connection.setSocketTimeout(originalTimeout); + } catch (IOException ignored) {} + } + } + + private int readLine(UnsyncBufferedInputStream in) throws IOException { + return in.readLine(responseLineBuffer, H1Connection.RESPONSE_LINE_BUFFER_SIZE); + } + + private void writeRequestLine(UnsyncBufferedOutputStream out) throws IOException { + out.writeAscii(request.method()); + out.write(' '); + + URI uri = request.uri(); + if (isHttpProxyWithoutTunnel()) { + out.writeAscii(uri.toString()); + } else { + String path = uri.getRawPath(); + if (path == null || path.isEmpty()) { + out.write('/'); + } else { + out.writeAscii(path); + } + + String query = uri.getRawQuery(); + if (query != null && !query.isEmpty()) { + out.write('?'); + out.writeAscii(query); + } + } + + out.write(HTTP_1_1_CRLF); + } + + /** + * Check if this request is going through an HTTP proxy without a tunnel. + * + * @return true if absolute URI format is needed + */ + private boolean isHttpProxyWithoutTunnel() { + return route != null && (route.usesProxy() && !route.isSecure()); + } + + private void writeHeaders(UnsyncBufferedOutputStream out, HttpHeaders headers) throws IOException { + // Ensure Host header is present + if (headers.firstValue("host") == null) { + URI uri = request.uri(); + out.write(HOST_HEADER); + out.writeAscii(uri.getHost()); + int port = uri.getPort(); + // Include port only if non-default for the scheme + if (port != -1 && port != defaultPort(uri.getScheme())) { + out.write(':'); + out.writeAscii(Integer.toString(port)); + } + out.write(CRLF); + } + + // Write all headers + for (var entry : headers) { + String name = entry.getKey(); + for (String value : entry.getValue()) { + out.writeAscii(name); + out.write(COLON_SPACE); + out.writeAscii(value); + out.write(CRLF); + } + } + + // Blank line to end headers + out.write(CRLF); + } + + private void ensureRequestComplete() throws IOException { + if (!requestWritten) { + if (requestOut != null) { + requestOut.close(); + } + connection.getOutputStream().flush(); + requestWritten = true; + } + } + + private void parseStatusLineAndHeaders() throws IOException { + // If we already parsed during Expect: 100-continue, return + if (statusCode != -1) { + return; + } + + UnsyncBufferedInputStream in = connection.getInputStream(); + + try { + // Loop to skip 1xx interim responses (RFC 9110 Section 15.2) + // Examples: 100 Continue, 103 Early Hints + int code; + do { + int lineLen = readLine(in); + if (lineLen <= 0) { + throw new IOException("Empty HTTP response from " + request.uri()); + } + code = parseStatusLine(responseLineBuffer, lineLen); + + if (code >= 100 && code < 200) { + // Skip 1xx interim response headers + while (readLine(in) > 0) { + // Discard header lines until empty line + } + } + } while (code >= 100 && code < 200); + + parseStatusAndHeaders(code, in); + } catch (SocketTimeoutException e) { + connection.markInactive(); + throw new SocketTimeoutException("Read timeout while waiting for HTTP response headers from " + + request.uri() + " (check read timeout configuration)"); + } + } + + /** + * Parse status line. Expects "HTTP/1.x NNN ...". + * Sets responseVersion and returns status code. + * Also detects HTTP/1.0 and disables keep-alive (HTTP/1.0 defaults to close). + */ + private int parseStatusLine(byte[] buf, int len) throws IOException { + // Validate HTTP version - must be "HTTP/1.0" or "HTTP/1.1". + // Minimum valid status line: "HTTP/1.x NNN" = 12 bytes + if (len < 12 + || buf[0] != 'H' + || buf[1] != 'T' + || buf[2] != 'T' + || buf[3] != 'P' + || buf[4] != '/' + || buf[5] != '1' + || buf[6] != '.' + || buf[8] != ' ') { + throw new IOException("Malformed HTTP response status line: " + + new String(buf, 0, len, StandardCharsets.US_ASCII)); + } + + byte minor = buf[7]; + if (minor == '1') { + responseVersion = HttpVersion.HTTP_1_1; + } else if (minor == '0') { + responseVersion = HttpVersion.HTTP_1_0; + connection.setKeepAlive(false); // HTTP/1.0 defaults to Connection: close + } else { + throw new IOException("Unsupported HTTP version: HTTP/1." + (char) minor); + } + + // Parse 3-digit status code directly from bytes (positions 9, 10, 11) + byte c1 = buf[9]; + byte c2 = buf[10]; + byte c3 = buf[11]; + + if (c1 < '0' || c1 > '9' || c2 < '0' || c2 > '9' || c3 < '0' || c3 > '9') { + throw new IOException("Invalid status code in HTTP response: " + + new String(buf, 0, len, StandardCharsets.US_ASCII)); + } + + return ((c1 - '0') * 100) + ((c2 - '0') * 10) + (c3 - '0'); + } + + private void parseStatusAndHeaders(int code, UnsyncBufferedInputStream in) throws IOException { + this.statusCode = code; + if (statusCode < 100 || statusCode > 599) { + throw new IOException("Invalid HTTP status code: " + statusCode); + } + + ModifiableHttpHeaders headers = HttpHeaders.ofModifiable(); + int headerCount = 0; + boolean sawConnectionClose = false; + + int lineLen; + while ((lineLen = readLine(in)) > 0) { + headerCount++; + if (headerCount > MAX_RESPONSE_HEADER_COUNT) { + throw new IOException("Too many HTTP headers: " + headerCount + + " exceeds maximum of " + MAX_RESPONSE_HEADER_COUNT); + } + + String name = HttpUtils.parseHeaderLine(responseLineBuffer, lineLen, headers); + if (name == null) { + throw new IOException("Invalid header line: " + + new String(responseLineBuffer, 0, lineLen, StandardCharsets.US_ASCII)); + } + + // Check Connection header to determine keep-alive behavior + // HTTP/1.1 defaults to keep-alive, HTTP/1.0 defaults to close + if ("connection".equals(name)) { + String value = headers.firstValue(name); + if ("close".equalsIgnoreCase(value)) { + sawConnectionClose = true; + } else if ("keep-alive".equalsIgnoreCase(value)) { + // Explicit keep-alive (needed for HTTP/1.0) + connection.setKeepAlive(true); + } + } + } + + this.responseHeaders = headers; + + if (sawConnectionClose) { + connection.setKeepAlive(false); + } + } + + private InputStream createResponseStream() throws IOException { + UnsyncBufferedInputStream socketIn = connection.getInputStream(); + + String transferEncoding = responseHeaders.firstValue("transfer-encoding"); + if (transferEncoding != null && containsChunked(transferEncoding)) { + chunkedResponseIn = new ChunkedInputStream(socketIn); + return chunkedResponseIn; + } + + String contentLength = responseHeaders.firstValue("content-length"); + if (contentLength != null) { + try { + long length = Long.parseLong(contentLength.trim()); + if (length < 0) { + throw new IOException("Invalid negative Content-Length: " + length); + } + return new BoundedInputStream(socketIn, length); + } catch (NumberFormatException e) { + throw new IOException("Invalid Content-Length header: " + contentLength); + } + } + + // No body for certain status codes or HEAD response. + if (noBodyResponseStatus(statusCode) || "HEAD".equalsIgnoreCase(request.method())) { + return new BoundedInputStream(socketIn, 0); + } + + // Read until close (HTTP/1.0 style) + connection.setKeepAlive(false); + return socketIn; + } + + /** + * Fast check for "chunked" token in transfer-encoding value. + */ + private static boolean containsChunked(String value) { + int len = value.length(); + if (len < 7) { + return false; + } + + // Fast path: exact match + if (value.equalsIgnoreCase("chunked")) { + return true; + } + + // Multi-value (rare): split and check each token + if (value.indexOf(',') >= 0) { + for (String token : value.split(",")) { + // Only allocates a string when needed + if (HeaderUtils.normalizeValue(token).equals("chunked")) { + return true; + } + } + } + + return false; + } + + /** + * Check if status code indicates no response body per RFC 9110 Section 6.4.1. + */ + private static boolean noBodyResponseStatus(int statusCode) { + return statusCode == 204 || statusCode == 304 || (statusCode >= 100 && statusCode < 200); + } + + /** + * Get default port for scheme (80 for http, 443 for https). + */ + private static int defaultPort(String scheme) { + if ("https".equalsIgnoreCase(scheme)) { + return 443; + } + return 80; // http or unknown + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/HttpUtils.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/HttpUtils.java new file mode 100644 index 000000000..cc47c3752 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/HttpUtils.java @@ -0,0 +1,133 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import java.nio.charset.StandardCharsets; +import software.amazon.smithy.java.http.api.ModifiableHttpHeaders; + +/** + * Interned HTTP header names to avoid String allocation for common headers. + * + *

Uses length-based switching and case-insensitive byte comparison to + * return pre-allocated String constants for well-known headers. + */ +final class HttpUtils { + + private static final String[] GROUP_4 = {"date", "vary", "etag"}; + private static final String[] GROUP_6 = {"server"}; + private static final String[] GROUP_7 = {"trailer", "expires", "upgrade"}; + private static final String[] GROUP_8 = {"location"}; + private static final String[] GROUP_10 = {"connection", "keep-alive", "set-cookie"}; + private static final String[] GROUP_12 = {"content-type"}; + private static final String[] GROUP_13 = {"cache-control", "last-modified", "content-range", "accept-ranges"}; + private static final String[] GROUP_14 = {"content-length"}; + private static final String[] GROUP_16 = { + "content-encoding", + "x-amzn-requestid", + "x-amz-request-id", + "www-authenticate", + "proxy-connection" + }; + private static final String[] GROUP_17 = {"transfer-encoding"}; + private static final String[] GROUP_18 = {"proxy-authenticate"}; + + private HttpUtils() {} + + /** + * Returns an interned String for common header names, or creates a new String for unknown headers. + * + * @param buf byte buffer containing header name + * @param start start offset in buffer + * @param len length of header name + * @return interned String for known headers, new String for unknown + */ + static String internHeader(byte[] buf, int start, int len) { + return switch (len) { + case 4 -> match(buf, start, len, GROUP_4); + case 6 -> match(buf, start, len, GROUP_6); + case 7 -> match(buf, start, len, GROUP_7); + case 8 -> match(buf, start, len, GROUP_8); + case 10 -> match(buf, start, len, GROUP_10); + case 12 -> match(buf, start, len, GROUP_12); + case 13 -> match(buf, start, len, GROUP_13); + case 14 -> match(buf, start, len, GROUP_14); + case 16 -> match(buf, start, len, GROUP_16); + case 17 -> match(buf, start, len, GROUP_17); + case 18 -> match(buf, start, len, GROUP_18); + default -> new String(buf, start, len, StandardCharsets.US_ASCII); + }; + } + + private static String match(byte[] buf, int start, int len, String[] group) { + for (String candidate : group) { + if (equalsIgnoreCase(buf, start, candidate)) { + return candidate; + } + } + return new String(buf, start, len, StandardCharsets.US_ASCII); + } + + private static boolean equalsIgnoreCase(byte[] buf, int start, String expected) { + for (int i = 0; i < expected.length(); i++) { + byte b = buf[start + i]; + // Convert to lowercase if uppercase ASCII letter + if (b >= 'A' && b <= 'Z') { + b += 32; + } + if (b != expected.charAt(i)) { + return false; + } + } + return true; + } + + /** + * Parse a header line and add it to the headers collection. + * + * @param buf byte buffer containing header line + * @param len length of header line (excluding CRLF) + * @param headers collection to add the parsed header to + * @return the interned header name, or null if line is malformed (no colon) + */ + static String parseHeaderLine(byte[] buf, int len, ModifiableHttpHeaders headers) { + // Find colon + int colon = -1; + for (int i = 0; i < len; i++) { + if (buf[i] == ':') { + colon = i; + break; + } + } + + if (colon <= 0) { + return null; + } + + // Intern header name + String name = internHeader(buf, 0, colon); + + // Find value bounds, skip leading/trailing OWS (space or tab per RFC 9110) + int valueStart = colon + 1; + int valueEnd = len; + while (valueStart < valueEnd && isOWS(buf[valueStart])) { + valueStart++; + } + while (valueEnd > valueStart && isOWS(buf[valueEnd - 1])) { + valueEnd--; + } + + String value = new String(buf, valueStart, valueEnd - valueStart, StandardCharsets.US_ASCII); + headers.addHeader(name, value); + return name; + } + + /** + * Check if byte is optional whitespace (OWS) per RFC 9110: SP or HTAB. + */ + private static boolean isOWS(byte b) { + return b == ' ' || b == '\t'; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java new file mode 100644 index 000000000..0ca60cf46 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java @@ -0,0 +1,986 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import static software.amazon.smithy.java.http.client.h2.H2Constants.CONNECTION_PREFACE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.DEFAULT_HEADER_TABLE_SIZE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.DEFAULT_INITIAL_WINDOW_SIZE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.DEFAULT_MAX_CONCURRENT_STREAMS; +import static software.amazon.smithy.java.http.client.h2.H2Constants.DEFAULT_MAX_FRAME_SIZE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_COMPRESSION_ERROR; +import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_ENHANCE_YOUR_CALM; +import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_FLOW_CONTROL_ERROR; +import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_NO_ERROR; +import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_PROTOCOL_ERROR; +import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_SETTINGS_TIMEOUT; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_ACK; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_END_HEADERS; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_END_STREAM; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_DATA; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_GOAWAY; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_HEADERS; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_PING; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_PUSH_PROMISE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_RST_STREAM; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_SETTINGS; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_WINDOW_UPDATE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.MAX_MAX_FRAME_SIZE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.MIN_MAX_FRAME_SIZE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.SETTINGS_ENABLE_PUSH; +import static software.amazon.smithy.java.http.client.h2.H2Constants.SETTINGS_HEADER_TABLE_SIZE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.SETTINGS_INITIAL_WINDOW_SIZE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.SETTINGS_MAX_CONCURRENT_STREAMS; +import static software.amazon.smithy.java.http.client.h2.H2Constants.SETTINGS_MAX_FRAME_SIZE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.SETTINGS_MAX_HEADER_LIST_SIZE; + +import java.io.IOException; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.time.Duration; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpExchange; +import software.amazon.smithy.java.http.client.UnsyncBufferedInputStream; +import software.amazon.smithy.java.http.client.UnsyncBufferedOutputStream; +import software.amazon.smithy.java.http.client.connection.HttpConnection; +import software.amazon.smithy.java.http.client.connection.Route; +import software.amazon.smithy.java.http.client.h2.hpack.HpackDecoder; +import software.amazon.smithy.java.logging.InternalLogger; + +/** + * HTTP/2 connection implementation with full stream multiplexing. + * + *

This implementation manages an HTTP/2 connection over a single TCP socket + * with support for multiple concurrent streams. A background reader thread + * dispatches incoming frames to the appropriate stream handlers. + * + *

Connection Lifecycle

+ *
    + *
  1. Constructor sends connection preface and SETTINGS
  2. + *
  3. Waits for server SETTINGS and sends ACK
  4. + *
  5. Starts background reader thread for frame dispatch
  6. + *
  7. {@link #newExchange} creates exchanges for requests
  8. + *
  9. {@link #close} sends GOAWAY and closes socket
  10. + *
+ * + *

Thread Safety

+ *

This class is thread-safe. Multiple virtual threads can create + * concurrent exchanges on the same connection. Frame writes are serialized + * via a dedicated writer thread with queue, and frame reads are handled by a + * dedicated reader thread. + */ +public final class H2Connection implements HttpConnection, H2StreamWriter.StreamManager { + /** + * Internal connection state. + */ + private enum State { + CONNECTED, + SHUTTING_DOWN, + CLOSED + } + + private static final InternalLogger LOGGER = InternalLogger.getLogger(H2Connection.class); + + private final Socket socket; + private final UnsyncBufferedOutputStream socketOut; + private final Route route; + private final H2FrameCodec frameCodec; + + // Combined encoder/writer - handles both HPACK encoding AND frame writing + // All socket writes are serialized through this single thread + private final H2StreamWriter streamWriter; + + // HPACK decoder for responses (only accessed by reader thread - no synchronization needed) + private final HpackDecoder hpackDecoder; + + // Connection settings (ours and peer's) + private volatile int remoteMaxFrameSize = DEFAULT_MAX_FRAME_SIZE; + private volatile int remoteInitialWindowSize = DEFAULT_INITIAL_WINDOW_SIZE; + private volatile int remoteMaxConcurrentStreams = DEFAULT_MAX_CONCURRENT_STREAMS; + private volatile int remoteHeaderTableSize = DEFAULT_HEADER_TABLE_SIZE; + // Server's limit for header list size we send (default: unlimited per RFC 9113) + private volatile int remoteMaxHeaderListSize = Integer.MAX_VALUE; + + // Flow control - use AtomicInteger for thread-safe updates + private final AtomicInteger connectionSendWindow = new AtomicInteger(DEFAULT_INITIAL_WINDOW_SIZE); + private volatile int connectionRecvWindow = DEFAULT_INITIAL_WINDOW_SIZE; + + // Our limit for received header list size (not from server SETTINGS) + private static final int DEFAULT_MAX_HEADER_LIST_SIZE = 8192; + + // Stream management - multiplexed! + private final ConcurrentHashMap activeStreams = new ConcurrentHashMap<>(); + private final AtomicInteger activeStreamCount = new AtomicInteger(0); + private volatile int lastStreamId = 0; + + // Background reader thread + private final Thread readerThread; + private volatile Throwable readerError; + + // Connection state + private volatile State state = State.CONNECTED; + private volatile boolean active = true; + private volatile boolean goawayReceived = false; + private volatile int goawayLastStreamId = Integer.MAX_VALUE; + + // Stream-level timeouts + private final Duration readTimeout; + private final Duration writeTimeout; + + /** + * Create an HTTP/2 connection from a connected socket. + * + *

The socket must already be connected and TLS handshake completed (if applicable). + * This constructor sends the connection preface and negotiates settings. + * + * @param socket the connected socket + * @param route the connection route + * @param readTimeout timeout for waiting on response data + * @param writeTimeout timeout for waiting on flow control window + * @throws IOException if connection preface fails + */ + public H2Connection(Socket socket, Route route, Duration readTimeout, Duration writeTimeout) throws IOException { + this.socket = socket; + // Use unsynchronized buffered streams (safe because reader/writer threads have exclusive access) + var socketIn = new UnsyncBufferedInputStream(socket.getInputStream(), 8192); + this.socketOut = new UnsyncBufferedOutputStream(socket.getOutputStream(), 8192); + this.route = route; + this.readTimeout = readTimeout; + this.writeTimeout = writeTimeout; + this.frameCodec = new H2FrameCodec(socketIn, socketOut, DEFAULT_MAX_FRAME_SIZE); + + // Initialize HPACK decoder for responses (only accessed by reader thread) + this.hpackDecoder = new HpackDecoder(DEFAULT_HEADER_TABLE_SIZE); + + // Perform connection preface BEFORE starting encoder thread + // (preface writes directly to frameCodec, encoder thread takes over after) + try { + sendConnectionPreface(); + receiveServerPreface(); + } catch (IOException e) { + close(); + throw new IOException("HTTP/2 connection preface failed", e); + } + + // Create combined encoder/writer AFTER connection preface + // This thread handles both HPACK encoding AND all frame writes + this.streamWriter = new H2StreamWriter( + this, + frameCodec, + DEFAULT_HEADER_TABLE_SIZE, + "h2-writer-" + route.host()); + + // Start background reader thread (dispatches incoming frames) + this.readerThread = Thread.ofVirtual() + .name("h2-reader-" + route.host()) + .start(this::readerLoop); + } + + /** + * Background reader loop - dispatches frames to streams. + */ + private void readerLoop() { + try { + while (state == State.CONNECTED) { + H2FrameCodec.Frame frame = frameCodec.readFrame(); + if (frame == null) { + // EOF - connection closed by peer + break; + } + + dispatchFrame(frame); + } + } catch (IOException e) { + if (state == State.CONNECTED) { + readerError = e; + active = false; + LOGGER.debug("Reader thread error for {}: {}", route, e.getMessage()); + } + } finally { + // Stop the encoder immediately on connection failure + // (don't wait for graceful drain - connection is dead) + if (streamWriter != null) { + streamWriter.shutdownNow(); + } + + // Signal all active streams that connection is closing + for (H2Exchange exchange : activeStreams.values()) { + exchange.signalConnectionClosed(readerError); + } + state = State.CLOSED; + + // Close socket to release resources promptly + // (don't wait for pool/caller to notice the connection is dead) + try { + socket.close(); + } catch (IOException ignored) { + // Best effort - socket may already be closed + } + } + } + + /** + * Dispatch a frame to the appropriate handler. + * + *

Stream-level frames are converted to {@link StreamEvent}s and enqueued + * to the exchange. HPACK decoding happens here to ensure dynamic table + * consistency across all streams. + */ + private void dispatchFrame(H2FrameCodec.Frame frame) throws IOException { + int streamId = frame.streamId(); + + if (streamId == 0) { + // Connection-level frame + handleConnectionFrame(frame); + } else { + // Handle HEADERS frames that may need CONTINUATION + if (frame.type() == FRAME_TYPE_HEADERS && !frame.hasFlag(FLAG_END_HEADERS)) { + // Read complete header block including CONTINUATION frames + byte[] headerBlock = frameCodec.readHeaderBlock(frame); + // Create a synthetic frame with complete headers + frame = new H2FrameCodec.Frame( + FRAME_TYPE_HEADERS, + frame.flags() | FLAG_END_HEADERS, + streamId, + headerBlock, + headerBlock.length); + } + + // Stream-level frame + H2Exchange exchange = activeStreams.get(streamId); + if (exchange != null) { + dispatchStreamFrame(exchange, frame, streamId); + } else { + // Frame for unknown stream + handleFrameForUnknownStream(frame, streamId); + } + } + } + + /** + * Handle frames received for streams not in our active set. + * + *

Per RFC 9113 Section 5.1, after sending RST_STREAM we must be prepared to receive + * and ignore additional frames that were in-flight. This commonly occurs when we close + * a stream before fully reading the response - the server may have already sent DATA. + * + *

For DATA frames: consume the connection recv window (for flow control) and ignore. + * For HEADERS frames: decode headers (to maintain HPACK state) and ignore. + * For other frames (WINDOW_UPDATE, RST_STREAM): silently ignore. + */ + private void handleFrameForUnknownStream(H2FrameCodec.Frame frame, int streamId) throws IOException { + if (frame.type() == FRAME_TYPE_DATA) { + // Consume connection-level flow control window for ignored DATA + byte[] payload = frame.payload(); + if (payload != null && payload.length > 0) { + consumeConnectionRecvWindow(payload.length); + } + LOGGER.trace("Ignoring DATA frame for closed stream {}", streamId); + } else if (frame.type() == FRAME_TYPE_HEADERS) { + // Must decode headers to keep HPACK decoder state in sync + byte[] headerBlock = frame.payload(); + if (headerBlock != null && headerBlock.length > 0) { + decodeHeaders(headerBlock); + } + LOGGER.trace("Ignoring HEADERS frame for closed stream {}", streamId); + } + // Other frame types (WINDOW_UPDATE, RST_STREAM) are silently ignored + } + + /** + * Dispatch a stream-level frame as a StreamEvent. + */ + private void dispatchStreamFrame(H2Exchange exchange, H2FrameCodec.Frame frame, int streamId) + throws IOException { + switch (frame.type()) { + case FRAME_TYPE_HEADERS -> { + // HPACK decoding MUST happen here in the reader thread to ensure + // dynamic table updates are processed in frame order across all streams. + byte[] headerBlock = frame.payload(); + List decoded; + if (headerBlock != null && headerBlock.length > 0) { + decoded = decodeHeaders(headerBlock); + } else { + decoded = List.of(); + } + + boolean endStream = frame.hasFlag(FLAG_END_STREAM); + exchange.enqueueEvent(new StreamEvent.Headers(decoded, endStream)); + } + + case FRAME_TYPE_DATA -> { + byte[] payload = frame.payload(); + boolean endStream = frame.hasFlag(FLAG_END_STREAM); + + // Handle flow control: update connection receive window + if (payload != null && payload.length > 0) { + consumeConnectionRecvWindow(payload.length); + } + + // Create DataChunk event + StreamEvent.DataChunk chunk; + if (payload != null && payload.length > 0) { + chunk = new StreamEvent.DataChunk(payload, 0, payload.length, endStream); + } else if (endStream) { + chunk = StreamEvent.DataChunk.END; + } else { + chunk = StreamEvent.DataChunk.EMPTY; + } + + exchange.enqueueEvent(chunk); + } + + case FRAME_TYPE_RST_STREAM -> { + // RST_STREAM: signal stream error so consumers fail fast + int errorCode = frame.parseRstStream(); + H2Exception error = new H2Exception(errorCode, + streamId, + "Stream reset by server: " + H2Constants.errorCodeName(errorCode)); + exchange.signalStreamError(error); + } + + case FRAME_TYPE_WINDOW_UPDATE -> { + // WINDOW_UPDATE affects send window, not response reading + int increment = frame.parseWindowUpdate(); + exchange.updateStreamSendWindow(increment); + } + + case FRAME_TYPE_PUSH_PROMISE -> { + // We disable push, so this is a protocol error + throw new H2Exception(ERROR_PROTOCOL_ERROR, + "Received PUSH_PROMISE but server push is disabled"); + } + + default -> { + // Unknown frame types are ignored per spec + } + } + } + + /** + * Handle connection-level frames (stream ID 0). + */ + private void handleConnectionFrame(H2FrameCodec.Frame frame) throws IOException { + switch (frame.type()) { + case FRAME_TYPE_SETTINGS -> { + if (!frame.hasFlag(FLAG_ACK)) { + applyRemoteSettings(frame); + // Submit SETTINGS ACK to writer thread + streamWriter.submitControlFrame(new H2StreamWriter.WorkItem.WriteSettingsAck()); + } + } + case FRAME_TYPE_PING -> { + if (!frame.hasFlag(FLAG_ACK)) { + // Submit PING ACK to writer thread + streamWriter.submitControlFrame(new H2StreamWriter.WorkItem.WritePing(frame.payload(), true)); + } + } + case FRAME_TYPE_GOAWAY -> { + int[] goaway = frame.parseGoaway(); + handleGoaway(goaway[0], goaway[1]); + } + case FRAME_TYPE_WINDOW_UPDATE -> { + int increment = frame.parseWindowUpdate(); + int newWindow = connectionSendWindow.addAndGet(increment); + // Check for overflow per RFC 9113 (wrap-around to negative) + if (newWindow < 0) { + throw new H2Exception(ERROR_FLOW_CONTROL_ERROR, + "Connection send window overflow: " + newWindow); + } + // Wake up any streams waiting for send window + for (H2Exchange exchange : activeStreams.values()) { + exchange.signalWindowUpdate(); + } + } + default -> { + // Ignore unknown connection-level frames + } + } + } + + /** + * Send client connection preface. + * RFC 9113 Section 3.4 + */ + private void sendConnectionPreface() throws IOException { + // 1. Send magic string + socketOut.write(CONNECTION_PREFACE); + + // 2. Send SETTINGS frame with our preferences + frameCodec.writeSettings( + SETTINGS_MAX_CONCURRENT_STREAMS, + 100, // We can handle 100 concurrent streams + SETTINGS_INITIAL_WINDOW_SIZE, + 65535, // Default 64KB window + SETTINGS_MAX_FRAME_SIZE, + 16384, // Default 16KB frames + SETTINGS_ENABLE_PUSH, + 0 // Disable server push + ); + frameCodec.flush(); + } + + // Timeout for receiving SETTINGS frame during connection setup (RFC 9113 Section 6.5.3) + private static final int SETTINGS_TIMEOUT_MS = 10_000; // 10 seconds + + /** + * Receive and process server preface. + * + *

RFC 9113 Section 6.5.3 recommends that implementations provide a way to + * bound the time within which a response to a SETTINGS frame is expected. + */ + private void receiveServerPreface() throws IOException { + // Set socket timeout for SETTINGS frame (RFC 9113 Section 6.5.3) + int originalTimeout = socket.getSoTimeout(); + try { + socket.setSoTimeout(SETTINGS_TIMEOUT_MS); + + // Read server's SETTINGS frame + H2FrameCodec.Frame frame; + try { + frame = frameCodec.readFrame(); + } catch (SocketTimeoutException e) { + throw new H2Exception(ERROR_SETTINGS_TIMEOUT, + "Timeout waiting for server SETTINGS frame"); + } + + if (frame == null) { + throw new IOException("Connection closed before receiving server SETTINGS"); + } + + if (frame.type() != FRAME_TYPE_SETTINGS) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + "Expected SETTINGS frame, got " + H2Constants.frameTypeName(frame.type())); + } + + if (frame.hasFlag(FLAG_ACK)) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + "First SETTINGS frame must not be ACK"); + } + + // Apply server settings + applyRemoteSettings(frame); + + // Send SETTINGS ACK + frameCodec.writeSettingsAck(); + frameCodec.flush(); + } finally { + // Restore original timeout (usually 0 = infinite for the reader loop) + socket.setSoTimeout(originalTimeout); + } + } + + /** + * Apply settings received from peer. + */ + private void applyRemoteSettings(H2FrameCodec.Frame frame) throws IOException { + int[] settings = frame.parseSettings(); + for (int i = 0; i < settings.length; i += 2) { + int id = settings[i]; + int value = settings[i + 1]; + + switch (id) { + case SETTINGS_HEADER_TABLE_SIZE: + remoteHeaderTableSize = value; + // Update encoder table size (applied on next encode) + streamWriter.setMaxTableSize(value); + break; + case SETTINGS_ENABLE_PUSH: + // We disable push, ignore server's preference + break; + case SETTINGS_MAX_CONCURRENT_STREAMS: + remoteMaxConcurrentStreams = value; + break; + case SETTINGS_INITIAL_WINDOW_SIZE: + // Value is unsigned 32-bit in protocol; negative means > 2^31-1 + if (value < 0) { + throw new H2Exception(ERROR_FLOW_CONTROL_ERROR, + "Invalid INITIAL_WINDOW_SIZE: " + (value & 0xFFFFFFFFL)); + } + // Update window for all active streams + int delta = value - remoteInitialWindowSize; + remoteInitialWindowSize = value; + for (H2Exchange exchange : activeStreams.values()) { + exchange.adjustSendWindow(delta); + } + break; + case SETTINGS_MAX_FRAME_SIZE: + if (value < MIN_MAX_FRAME_SIZE || value > MAX_MAX_FRAME_SIZE) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + "Invalid MAX_FRAME_SIZE: " + value); + } + remoteMaxFrameSize = value; + break; + case SETTINGS_MAX_HEADER_LIST_SIZE: + // Server's limit for header list size we can send + remoteMaxHeaderListSize = value; + break; + default: + // Unknown settings are ignored per spec + break; + } + } + } + + @Override + public HttpExchange newExchange(HttpRequest request) throws IOException { + if (state != State.CONNECTED) { + throw new IOException("Connection is not in CONNECTED state: " + state); + } + + if (goawayReceived) { + // Check if new stream ID would exceed the last allowed stream + int nextId = streamWriter.getNextStreamId(); + if (nextId > goawayLastStreamId) { + throw new IOException("Connection received GOAWAY with lastStreamId=" + + goawayLastStreamId + ", cannot create stream " + nextId); + } + } + + // Fast-fail check (encoder will recheck under serialization) + if (activeStreamCount.get() >= remoteMaxConcurrentStreams) { + throw new IOException("Connection at max concurrent streams: " + activeStreamCount.get()); + } + + // Pre-create the exchange (stream ID will be set by encoder) + H2Exchange exchange = new H2Exchange(this, request, readTimeout, writeTimeout); + + // Determine if request has a body + boolean hasBody = request.body() != null && request.body().contentLength() != 0; + boolean endStream = !hasBody; + + CompletableFuture streamIdFuture = new CompletableFuture<>(); + CompletableFuture writeComplete = new CompletableFuture<>(); + + // Submit to writer thread (blocks if queue is full, provides backpressure) + var encodeHeaders = new H2StreamWriter.WorkItem.EncodeHeaders( + request, + exchange, + endStream, + streamIdFuture, + writeComplete); + + if (!streamWriter.submitWork(encodeHeaders, writeTimeout.toMillis())) { + throw new IOException("Write queue full - connection overloaded (timeout after " + writeTimeout + ")"); + } + + // Wait for encoding to complete (stream ID assigned, queued to writer) + int streamId; + try { + streamId = streamIdFuture.join(); + } catch (Exception e) { + Throwable cause = e.getCause(); + if (cause instanceof IOException ioe) { + throw ioe; + } + throw new IOException("Encoding failed", cause != null ? cause : e); + } + + // Wait for write to complete + try { + writeComplete.join(); + } catch (Exception e) { + // Write failed - cleanup and rethrow + activeStreams.remove(streamId); + activeStreamCount.decrementAndGet(); + Throwable cause = e.getCause(); + if (cause instanceof IOException ioe) { + throw ioe; + } + throw new IOException("Failed to write headers", cause != null ? cause : e); + } + + return exchange; + } + + /** + * Unregister a stream when it completes. + */ + @Override + public void unregisterStream(int streamId) { + if (activeStreams.remove(streamId) != null) { + activeStreamCount.decrementAndGet(); + } + } + + // ==================== StreamManager interface implementation ==================== + + @Override + public boolean tryReserveStream() { + while (true) { + int current = activeStreamCount.get(); + if (current >= remoteMaxConcurrentStreams) { + return false; + } + if (activeStreamCount.compareAndSet(current, current + 1)) { + return true; + } + } + } + + @Override + public void releaseStreamSlot() { + activeStreamCount.decrementAndGet(); + } + + @Override + public void registerStream(int streamId, H2Exchange exchange) { + activeStreams.put(streamId, exchange); + } + + @Override + public void setLastStreamId(int streamId) { + this.lastStreamId = streamId; + } + + @Override + public boolean isAcceptingStreams() { + return state == State.CONNECTED && !goawayReceived; + } + + @Override + public int getRemoteMaxHeaderListSize() { + return remoteMaxHeaderListSize; + } + + // ==================== End StreamManager interface ==================== + + /** + * Queue a DATA frame for writing via the encoder/writer thread. + * + *

Flow control must already be checked by the caller. This method + * queues the write and blocks until it completes. + * + * @param streamId the stream ID + * @param data the data buffer + * @param offset offset into the buffer + * @param length number of bytes to write + * @param flags frame flags (e.g., FLAG_END_STREAM) + * @throws IOException if the write fails + */ + void queueData(int streamId, byte[] data, int offset, int length, int flags) throws IOException { + CompletableFuture completion = new CompletableFuture<>(); + if (!streamWriter.submitWork( + new H2StreamWriter.WorkItem.WriteData(streamId, data, offset, length, flags, completion), + writeTimeout.toMillis())) { + throw new IOException("Write queue full - connection overloaded"); + } + + try { + completion.join(); + } catch (Exception e) { + Throwable cause = e.getCause(); + if (cause instanceof IOException ioe) { + throw ioe; + } + throw new IOException("Failed to write data", cause != null ? cause : e); + } + } + + /** + * Get the current number of active streams. + */ + public int getActiveStreamCount() { + return activeStreamCount.get(); + } + + /** + * Check if connection can accept more streams. + */ + public boolean canAcceptMoreStreams() { + return isActive() && activeStreamCount.get() < remoteMaxConcurrentStreams; + } + + @Override + public HttpVersion httpVersion() { + return HttpVersion.HTTP_2; + } + + @Override + public boolean isActive() { + return active; + } + + @Override + public boolean validateForReuse() { + if (!active) { + return false; + } + + // Check socket state + if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) { + LOGGER.debug("Connection to {} is closed or half-closed", route); + active = false; + state = State.CLOSED; + return false; + } + + return true; + } + + @Override + public Route route() { + return route; + } + + @Override + public SSLSession sslSession() { + if (socket instanceof SSLSocket sslSocket) { + return sslSocket.getSession(); + } + return null; + } + + @Override + public String negotiatedProtocol() { + if (socket instanceof SSLSocket sslSocket) { + String protocol = sslSocket.getApplicationProtocol(); + return (protocol != null && !protocol.isEmpty()) ? protocol : "h2"; + } + return "h2"; + } + + // Timeout for graceful shutdown - allows pending writes to complete + private static final int GRACEFUL_SHUTDOWN_MS = 1000; + + @Override + public void close() throws IOException { + if (state == State.CLOSED) { + return; + } + + active = false; + + // Handle both CONNECTED and SHUTTING_DOWN states + State previousState = state; + state = State.SHUTTING_DOWN; + + // Send GOAWAY before closing encoder + if (previousState == State.CONNECTED) { + streamWriter.submitControlFrame( + new H2StreamWriter.WorkItem.WriteGoaway(lastStreamId, ERROR_NO_ERROR, null)); + } + + // Close encoder (gracefully drains pending requests, sends GOAWAY, then stops) + if (streamWriter != null) { + streamWriter.close(); + } + + state = State.CLOSED; + + // Close socket - this will unblock the reader thread's blocking read. + // Thread.interrupt() doesn't work for socket I/O. + socket.close(); + + // Wait briefly for reader thread to notice the close + if (readerThread != null) { + try { + readerThread.join(100); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Decode HPACK header block. + * + *

IMPORTANT: This method MUST be called only from the reader thread, + * in frame arrival order. HPACK uses a connection-global dynamic table that + * is updated during decoding. If headers are decoded out of wire order (e.g., + * by different stream threads racing), the dynamic table state becomes corrupted + * and subsequent header blocks will decode incorrectly. + * + *

Per RFC 9113 Section 4.3, HPACK decoding errors MUST be treated as + * connection errors of type COMPRESSION_ERROR. + * + * @param headerBlock the encoded header block + * @return decoded header fields + * @throws IOException if decoding fails + * @throws H2Exception if header list size exceeds limit or HPACK decoding fails + */ + List decodeHeaders(byte[] headerBlock) throws IOException { + // Check encoded size first (quick rejection) + int maxHeaderListSize = DEFAULT_MAX_HEADER_LIST_SIZE; + if (headerBlock.length > maxHeaderListSize) { + throw new H2Exception(ERROR_ENHANCE_YOUR_CALM, + "Header block size " + headerBlock.length + " exceeds limit " + maxHeaderListSize); + } + + List headers; + // HPACK decoder is stateful (dynamic table), must be synchronized + synchronized (hpackDecoder) { + try { + headers = hpackDecoder.decodeBlock(headerBlock, 0, headerBlock.length); + } catch (IOException e) { + // RFC 9113 Section 4.3: HPACK decoding errors are COMPRESSION_ERROR + active = false; + LOGGER.debug("HPACK decoding failed for {}: {}", route, e.getMessage()); + throw new H2Exception(ERROR_COMPRESSION_ERROR, "HPACK decoding failed: " + e.getMessage()); + } catch (IndexOutOfBoundsException e) { + // Dynamic table index out of range - HPACK state mismatch + // This is a fatal connection error per RFC 9113 Section 4.3 + active = false; + LOGGER.debug("HPACK dynamic table mismatch for {}: {}", route, e.getMessage()); + throw new H2Exception(ERROR_COMPRESSION_ERROR, "HPACK state mismatch: " + e.getMessage()); + } + } + + // Check decoded size (name + value + 32 bytes overhead per RFC 7541) + int decodedSize = 0; + for (HpackDecoder.HeaderField field : headers) { + decodedSize += field.name().length() + field.value().length() + 32; + if (decodedSize > maxHeaderListSize) { + throw new H2Exception(ERROR_ENHANCE_YOUR_CALM, + "Decoded header list size exceeds limit " + maxHeaderListSize); + } + } + + return headers; + } + + /** + * Get the remote initial window size. + */ + int getRemoteInitialWindowSize() { + return remoteInitialWindowSize; + } + + /** + * Get the remote max frame size. + */ + int getRemoteMaxFrameSize() { + return remoteMaxFrameSize; + } + + /** + * Update connection-level send window. + * + * @param delta window size change (positive for WINDOW_UPDATE) + */ + void updateConnectionSendWindow(int delta) { + connectionSendWindow.addAndGet(delta); + } + + /** + * Get current connection send window. + */ + int getConnectionSendWindow() { + return connectionSendWindow.get(); + } + + /** + * Consume bytes from connection send window. + * + * @param bytes number of bytes to consume + */ + void consumeConnectionSendWindow(int bytes) { + connectionSendWindow.addAndGet(-bytes); + } + + /** + * Queue a stream-level WINDOW_UPDATE frame. + * + * @param streamId the stream ID + * @param increment the window size increment + * @throws IOException if the write queue is full + */ + void queueWindowUpdate(int streamId, int increment) throws IOException { + if (!streamWriter.submitControlFrame( + new H2StreamWriter.WorkItem.WriteWindowUpdate(streamId, increment))) { + throw new IOException("Write queue full - cannot send WINDOW_UPDATE"); + } + } + + /** + * Queue a RST_STREAM frame (fire-and-forget, doesn't wait for completion). + * + * @param streamId the stream ID + * @param errorCode the error code + * @throws IOException if the write queue is full + */ + void queueRst(int streamId, int errorCode) throws IOException { + // Fire-and-forget - no need to wait for completion + if (!streamWriter.submitControlFrame( + new H2StreamWriter.WorkItem.WriteRst(streamId, errorCode, new CompletableFuture<>()))) { + throw new IOException("Write queue full - cannot send RST_STREAM"); + } + } + + /** + * Consume bytes from the connection receive window. + * + *

In HTTP/2, data received counts against both the stream window AND + * the connection window. This method tracks connection-level consumption + * and queues WINDOW_UPDATE when the window gets low. + * + * @param bytes number of bytes received + * @throws IOException if queue is full after timeout or thread is interrupted + */ + void consumeConnectionRecvWindow(int bytes) throws IOException { + int newWindow; + int increment = 0; + synchronized (this) { + connectionRecvWindow -= bytes; + newWindow = connectionRecvWindow; + + // Send WINDOW_UPDATE when window gets below half + if (newWindow < DEFAULT_INITIAL_WINDOW_SIZE / 2) { + increment = DEFAULT_INITIAL_WINDOW_SIZE - newWindow; + connectionRecvWindow += increment; + } + } + + if (increment > 0) { + // Queue connection-level WINDOW_UPDATE + if (!streamWriter.submitWork( + new H2StreamWriter.WorkItem.WriteWindowUpdate(0, increment), + writeTimeout.toMillis())) { + throw new IOException("Write queue full - cannot send connection WINDOW_UPDATE"); + } + } + } + + /** + * Handle GOAWAY frame from server. + * + *

Per RFC 9113 Section 6.8, streams with IDs greater than the last stream ID + * that was processed by the server should be considered refused and retried. + */ + void handleGoaway(int lastStreamId, int errorCode) { + goawayReceived = true; + goawayLastStreamId = lastStreamId; + active = false; + + if (errorCode != ERROR_NO_ERROR) { + LOGGER.debug("Server sent GOAWAY to {}: {}", route, H2Constants.errorCodeName(errorCode)); + } + + state = State.SHUTTING_DOWN; + + // RFC 9113 Section 6.8: Signal streams with ID > lastStreamId that they were refused + // These streams were initiated but not processed by the server + H2Exception refusedError = new H2Exception(errorCode, + "Stream affected by GOAWAY (lastStreamId=" + lastStreamId + + ", error=" + H2Constants.errorCodeName(errorCode) + ")"); + for (var entry : activeStreams.entrySet()) { + int streamId = entry.getKey(); + if (streamId > lastStreamId) { + H2Exchange exchange = entry.getValue(); + exchange.signalConnectionClosed(refusedError); + } + } + + // The encoder will handle failing queued requests when it shuts down + // Streams > lastStreamId that are already queued will fail when processed + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java new file mode 100644 index 000000000..7dfa21f0f --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java @@ -0,0 +1,131 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import java.nio.charset.StandardCharsets; + +/** + * HTTP/2 protocol constants from RFC 9113. + */ +final class H2Constants { + + private H2Constants() {} + + // Connection preface - client must send this first (RFC 9113 Section 3.4) + static final byte[] CONNECTION_PREFACE = + "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.US_ASCII); + + // Frame header size is always 9 bytes + static final int FRAME_HEADER_SIZE = 9; + + // Frame types (RFC 9113 Section 6) + static final int FRAME_TYPE_DATA = 0x0; + static final int FRAME_TYPE_HEADERS = 0x1; + static final int FRAME_TYPE_PRIORITY = 0x2; + static final int FRAME_TYPE_RST_STREAM = 0x3; + static final int FRAME_TYPE_SETTINGS = 0x4; + static final int FRAME_TYPE_PUSH_PROMISE = 0x5; + static final int FRAME_TYPE_PING = 0x6; + static final int FRAME_TYPE_GOAWAY = 0x7; + static final int FRAME_TYPE_WINDOW_UPDATE = 0x8; + static final int FRAME_TYPE_CONTINUATION = 0x9; + + // Frame flags + static final int FLAG_END_STREAM = 0x1; // DATA, HEADERS + static final int FLAG_END_HEADERS = 0x4; // HEADERS, PUSH_PROMISE, CONTINUATION + static final int FLAG_PADDED = 0x8; // DATA, HEADERS, PUSH_PROMISE + static final int FLAG_PRIORITY = 0x20; // HEADERS + static final int FLAG_ACK = 0x1; // SETTINGS, PING + + // Settings identifiers (RFC 9113 Section 6.5.2) + static final int SETTINGS_HEADER_TABLE_SIZE = 0x1; + static final int SETTINGS_ENABLE_PUSH = 0x2; + static final int SETTINGS_MAX_CONCURRENT_STREAMS = 0x3; + static final int SETTINGS_INITIAL_WINDOW_SIZE = 0x4; + static final int SETTINGS_MAX_FRAME_SIZE = 0x5; + static final int SETTINGS_MAX_HEADER_LIST_SIZE = 0x6; + static final int SETTINGS_NO_RFC7540_PRIORITIES = 0x9; // RFC 9113 - disable deprecated priority + + // Default settings values + static final int DEFAULT_HEADER_TABLE_SIZE = 4096; + static final int DEFAULT_ENABLE_PUSH = 1; + static final int DEFAULT_MAX_CONCURRENT_STREAMS = Integer.MAX_VALUE; + static final int DEFAULT_INITIAL_WINDOW_SIZE = 65535; + static final int DEFAULT_MAX_FRAME_SIZE = 16384; + static final int DEFAULT_MAX_HEADER_LIST_SIZE = Integer.MAX_VALUE; + + // Frame size limits + static final int MIN_MAX_FRAME_SIZE = 16384; // 2^14 + static final int MAX_MAX_FRAME_SIZE = 16777215; // 2^24 - 1 + + // Window size limits + static final int MAX_WINDOW_SIZE = Integer.MAX_VALUE; // 2^31 - 1 + + // Error codes (RFC 9113 Section 7) + static final int ERROR_NO_ERROR = 0x0; + static final int ERROR_PROTOCOL_ERROR = 0x1; + static final int ERROR_INTERNAL_ERROR = 0x2; + static final int ERROR_FLOW_CONTROL_ERROR = 0x3; + static final int ERROR_SETTINGS_TIMEOUT = 0x4; + static final int ERROR_STREAM_CLOSED = 0x5; + static final int ERROR_FRAME_SIZE_ERROR = 0x6; + static final int ERROR_REFUSED_STREAM = 0x7; + static final int ERROR_CANCEL = 0x8; + static final int ERROR_COMPRESSION_ERROR = 0x9; + static final int ERROR_CONNECT_ERROR = 0xa; + static final int ERROR_ENHANCE_YOUR_CALM = 0xb; + static final int ERROR_INADEQUATE_SECURITY = 0xc; + static final int ERROR_HTTP_1_1_REQUIRED = 0xd; + + // Pseudo-header field names + static final String PSEUDO_METHOD = ":method"; + static final String PSEUDO_SCHEME = ":scheme"; + static final String PSEUDO_AUTHORITY = ":authority"; + static final String PSEUDO_PATH = ":path"; + static final String PSEUDO_STATUS = ":status"; + + /** + * Get error code name for debugging. + */ + static String errorCodeName(int code) { + return switch (code) { + case ERROR_NO_ERROR -> "NO_ERROR"; + case ERROR_PROTOCOL_ERROR -> "PROTOCOL_ERROR"; + case ERROR_INTERNAL_ERROR -> "INTERNAL_ERROR"; + case ERROR_FLOW_CONTROL_ERROR -> "FLOW_CONTROL_ERROR"; + case ERROR_SETTINGS_TIMEOUT -> "SETTINGS_TIMEOUT"; + case ERROR_STREAM_CLOSED -> "STREAM_CLOSED"; + case ERROR_FRAME_SIZE_ERROR -> "FRAME_SIZE_ERROR"; + case ERROR_REFUSED_STREAM -> "REFUSED_STREAM"; + case ERROR_CANCEL -> "CANCEL"; + case ERROR_COMPRESSION_ERROR -> "COMPRESSION_ERROR"; + case ERROR_CONNECT_ERROR -> "CONNECT_ERROR"; + case ERROR_ENHANCE_YOUR_CALM -> "ENHANCE_YOUR_CALM"; + case ERROR_INADEQUATE_SECURITY -> "INADEQUATE_SECURITY"; + case ERROR_HTTP_1_1_REQUIRED -> "HTTP_1_1_REQUIRED"; + default -> "UNKNOWN(" + code + ")"; + }; + } + + /** + * Get frame type name for debugging. + */ + static String frameTypeName(int type) { + return switch (type) { + case FRAME_TYPE_DATA -> "DATA"; + case FRAME_TYPE_HEADERS -> "HEADERS"; + case FRAME_TYPE_PRIORITY -> "PRIORITY"; + case FRAME_TYPE_RST_STREAM -> "RST_STREAM"; + case FRAME_TYPE_SETTINGS -> "SETTINGS"; + case FRAME_TYPE_PUSH_PROMISE -> "PUSH_PROMISE"; + case FRAME_TYPE_PING -> "PING"; + case FRAME_TYPE_GOAWAY -> "GOAWAY"; + case FRAME_TYPE_WINDOW_UPDATE -> "WINDOW_UPDATE"; + case FRAME_TYPE_CONTINUATION -> "CONTINUATION"; + default -> "UNKNOWN(" + type + ")"; + }; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java new file mode 100644 index 000000000..0e4a6c59f --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java @@ -0,0 +1,86 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Input stream for reading response body from DATA frames. + */ +final class H2DataInputStream extends InputStream { + private final H2Exchange exchange; + private StreamEvent.DataChunk currentChunk; + private int chunkPos; + private boolean closed = false; + private boolean eof = false; + + H2DataInputStream(H2Exchange exchange) { + this.exchange = exchange; + } + + @Override + public int read() throws IOException { + if (closed || eof) { + return -1; + } else if ((currentChunk == null || chunkPos >= currentChunk.length()) && !loadNextChunk()) { + return -1; + } + + return currentChunk.data()[currentChunk.offset() + chunkPos++] & 0xFF; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (closed || eof) { + return -1; + } else if (len == 0) { + return 0; + } + + int totalRead = 0; + while (len > 0) { + if (currentChunk == null || chunkPos >= currentChunk.length()) { + if (!loadNextChunk()) { + return totalRead > 0 ? totalRead : -1; + } + } + + int available = currentChunk.length() - chunkPos; + int toCopy = Math.min(available, len); + System.arraycopy(currentChunk.data(), currentChunk.offset() + chunkPos, b, off, toCopy); + chunkPos += toCopy; + off += toCopy; + len -= toCopy; + totalRead += toCopy; + } + + return totalRead; + } + + private boolean loadNextChunk() throws IOException { + currentChunk = exchange.readDataChunk(); + chunkPos = 0; + if (currentChunk.isEnd()) { + eof = true; + return false; + } + return true; + } + + @Override + public int available() { + if (currentChunk != null && !currentChunk.isEnd()) { + return currentChunk.length() - chunkPos; + } + return 0; + } + + @Override + public void close() { + closed = true; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataOutputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataOutputStream.java new file mode 100644 index 000000000..70dedb19d --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataOutputStream.java @@ -0,0 +1,76 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Output stream for writing request body as DATA frames. + */ +final class H2DataOutputStream extends OutputStream { + private final H2Exchange exchange; + private final byte[] buffer; + private int pos = 0; + private boolean closed = false; + + H2DataOutputStream(H2Exchange exchange, int bufferSize) { + this.exchange = exchange; + this.buffer = new byte[Math.max(bufferSize, 1)]; + } + + @Override + public void write(int b) throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + buffer[pos++] = (byte) b; + if (pos >= buffer.length) { + flush(); + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (closed) + throw new IOException("Stream closed"); + while (len > 0) { + int space = buffer.length - pos; + int toCopy = Math.min(space, len); + System.arraycopy(b, off, buffer, pos, toCopy); + pos += toCopy; + off += toCopy; + len -= toCopy; + if (pos >= buffer.length) { + flush(); + } + } + } + + @Override + public void flush() throws IOException { + if (pos > 0) { + exchange.writeData(buffer, 0, pos, false); + pos = 0; + } + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + closed = true; + + // Flush remaining data with END_STREAM + if (pos > 0) { + exchange.writeData(buffer, 0, pos, true); + pos = 0; + } else { + exchange.sendEndStream(); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exception.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exception.java new file mode 100644 index 000000000..0e8c812e5 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exception.java @@ -0,0 +1,74 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import static software.amazon.smithy.java.http.client.h2.H2Constants.errorCodeName; + +import java.io.IOException; + +/** + * Exception representing an HTTP/2 protocol error. + * + *

This exception carries an HTTP/2 error code that can be used to send + * RST_STREAM or GOAWAY frames to the peer. + */ +public final class H2Exception extends IOException { + + private final int errorCode; + private final int streamId; + + /** + * Create a connection-level HTTP/2 exception. + * + * @param errorCode HTTP/2 error code + * @param message error message + */ + public H2Exception(int errorCode, String message) { + super(message + " (" + errorCodeName(errorCode) + ")"); + this.errorCode = errorCode; + this.streamId = 0; + } + + /** + * Create a stream-level HTTP/2 exception. + * + * @param errorCode HTTP/2 error code + * @param streamId affected stream ID + * @param message error message + */ + public H2Exception(int errorCode, int streamId, String message) { + super("Stream " + streamId + ": " + message + " (" + errorCodeName(errorCode) + ")"); + this.errorCode = errorCode; + this.streamId = streamId; + } + + /** + * Get the HTTP/2 error code. + * + * @return error code + */ + public int errorCode() { + return errorCode; + } + + /** + * Get the affected stream ID. + * + * @return stream ID, or 0 for connection-level errors + */ + public int streamId() { + return streamId; + } + + /** + * Whether this is a connection-level error. + * + * @return true if connection-level, false if stream-level + */ + public boolean isConnectionError() { + return streamId == 0; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java new file mode 100644 index 000000000..f03d0ae3c --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -0,0 +1,785 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import static software.amazon.smithy.java.http.client.h2.H2Constants.DEFAULT_INITIAL_WINDOW_SIZE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_CANCEL; +import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_FLOW_CONTROL_ERROR; +import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_PROTOCOL_ERROR; +import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_STREAM_CLOSED; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_END_STREAM; +import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_STATUS; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.SocketTimeoutException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.api.ModifiableHttpHeaders; +import software.amazon.smithy.java.http.client.DelegatedClosingInputStream; +import software.amazon.smithy.java.http.client.DelegatedClosingOutputStream; +import software.amazon.smithy.java.http.client.HttpExchange; +import software.amazon.smithy.java.http.client.h2.hpack.HpackDecoder; + +/** + * HTTP/2 exchange implementation for a single stream with multiplexing support. + * + *

This class manages the lifecycle of a single HTTP/2 stream (request/response pair). + * Events (headers, data, errors) are received from the connection's reader thread via a + * single {@link StreamEvent} queue. + * + *

Stream Lifecycle

+ *
    + *
  1. Constructor sends HEADERS frame
  2. + *
  3. {@link #requestBody()} returns output stream for DATA frames
  4. + *
  5. {@link #responseHeaders()}/{@link #responseStatusCode()} read response HEADERS
  6. + *
  7. {@link #responseBody()} returns input stream for response DATA frames
  8. + *
  9. {@link #close()} sends RST_STREAM if needed and unregisters stream
  10. + *
+ */ +public final class H2Exchange implements HttpExchange { + + /** + * Stream states per RFC 9113 Section 5.1. + */ + enum StreamState { + IDLE, // Initial state + OPEN, // After sending HEADERS without END_STREAM + HALF_CLOSED_LOCAL, // We sent END_STREAM, can still receive + HALF_CLOSED_REMOTE, // They sent END_STREAM, can still send + CLOSED // Both directions closed + } + + // Request pseudo-headers (only allowed in requests, not responses) + private static final Set REQUEST_PSEUDO_HEADERS = Set.of( + ":method", + ":scheme", + ":authority", + ":path"); + + // Shared empty array to avoid allocation + private static final byte[] EMPTY_DATA = new byte[0]; + + private final H2Connection connection; + private final HttpRequest request; + private volatile int streamId; + + // Unified event queue - all stream events flow through here + // Producer: connection's reader thread + // Consumer: exchange methods (readResponseHeaders, readDataChunk) + private final BlockingQueue eventQueue = new LinkedBlockingQueue<>(); + + // Stream state machine per RFC 9113 Section 5.1 + private volatile StreamState streamState = StreamState.IDLE; + + // Stream-level timeouts + private final long readTimeoutMs; + private final long writeTimeoutMs; + + // Response state + private volatile int statusCode = -1; + private volatile HttpHeaders responseHeaders; + private volatile boolean responseHeadersReceived = false; + private volatile boolean endStreamReceived = false; + + // Informational responses (1xx) per RFC 9113 Section 8.1.1 + private final List informationalResponses = new ArrayList<>(); + + // Trailer headers per RFC 9113 Section 8.1 + private volatile HttpHeaders trailerHeaders; + + // Content-Length validation per RFC 9113 Section 8.1.1 + private long expectedContentLength = -1; // -1 means not specified + private long receivedContentLength = 0; + + // Request state + private volatile boolean endStreamSent = false; + private volatile OutputStream requestOut; + + // Response body input stream + private volatile InputStream responseIn; + + // Close guard + private final AtomicBoolean closed = new AtomicBoolean(false); + + // Auto-close tracking: exchange closes when both streams are closed (count reaches 2) + private final AtomicInteger closedStreamCount = new AtomicInteger(0); + + // Flow control with signaling + // streamSendWindow is protected by flowControlLock (accessed by writer and reader threads) + // streamRecvWindow is only accessed by application thread in readDataChunk() (single-threaded) + private final ReentrantLock flowControlLock = new ReentrantLock(); + private final Condition windowAvailable = flowControlLock.newCondition(); + private int streamSendWindow; + private int streamRecvWindow; + + /** + * Create a new HTTP/2 exchange without a stream ID. + * + *

The stream ID will be assigned later via {@link #setStreamId} when + * the connection allocates it under lock. This allows exchange construction + * to happen outside the critical section. + * + * @param connection the HTTP/2 connection + * @param request the HTTP request + * @param readTimeout timeout for waiting on response data + * @param writeTimeout timeout for waiting on flow control window + */ + H2Exchange(H2Connection connection, HttpRequest request, Duration readTimeout, Duration writeTimeout) { + this.connection = connection; + this.request = request; + this.streamId = -1; // Will be set later + this.readTimeoutMs = readTimeout.toMillis(); + this.writeTimeoutMs = writeTimeout.toMillis(); + this.streamSendWindow = connection.getRemoteInitialWindowSize(); + this.streamRecvWindow = DEFAULT_INITIAL_WINDOW_SIZE; + } + + /** + * Set the stream ID. Called by connection when allocating under lock. + */ + void setStreamId(int streamId) { + this.streamId = streamId; + } + + /** + * Called by connection after headers are encoded but before they're written. + * + *

Updates the exchange state to reflect that headers have been queued for sending. + * The actual I/O happens outside the HPACK lock for better concurrency. + * + * @param endStream true if the HEADERS frame will have END_STREAM flag + */ + void onHeadersEncoded(boolean endStream) { + if (endStream) { + endStreamSent = true; + streamState = StreamState.HALF_CLOSED_LOCAL; + } else { + streamState = StreamState.OPEN; + } + } + + /** + * Called by connection's reader thread to deliver an event. + * + *

All stream events (headers, data, errors) flow through this single method. + * The reader thread is the only producer; exchange methods are the only consumers. + */ + void enqueueEvent(StreamEvent event) { + eventQueue.add(event); + } + + /** + * Called by connection when it's closing. + * + *

Note: We set endStreamReceived but NOT streamState here. Setting streamState + * would race with pending events in the queue - handleHeadersEvent checks + * streamState and would throw a protocol error for legitimate pending events. + * The error event will be processed after pending events. + */ + void signalConnectionClosed(Throwable error) { + this.endStreamReceived = true; + IOException cause = (error instanceof IOException ioe) ? ioe : new IOException("Connection closed", error); + eventQueue.add(new StreamEvent.ConnectionError(cause)); + } + + /** + * Called by reader thread when a per-stream error occurs (e.g., RST_STREAM). + * + *

This allows readResponseHeaders() and readDataChunk() to fail fast with + * a meaningful error instead of timing out. + * + *

Note: We set endStreamReceived but NOT streamState to avoid racing with + * pending events in the queue. + */ + void signalStreamError(H2Exception error) { + this.endStreamReceived = true; + eventQueue.add(new StreamEvent.StreamError(error)); + } + + /** + * Called by connection when WINDOW_UPDATE is received. + */ + void signalWindowUpdate() { + flowControlLock.lock(); + try { + windowAvailable.signalAll(); + } finally { + flowControlLock.unlock(); + } + } + + /** + * Called by connection when SETTINGS changes initial window size. + */ + void adjustSendWindow(int delta) { + flowControlLock.lock(); + try { + streamSendWindow += delta; + if (streamSendWindow > 0) { + windowAvailable.signalAll(); + } + } finally { + flowControlLock.unlock(); + } + } + + @Override + public HttpRequest request() { + return request; + } + + @Override + public OutputStream requestBody() { + if (requestOut == null) { + // If no request body is expected, then return a no-op stream. + H2DataOutputStream rawOut = endStreamSent + ? new H2DataOutputStream(this, 0) + : new H2DataOutputStream(this, connection.getRemoteMaxFrameSize()); + requestOut = new DelegatedClosingOutputStream(rawOut, this::onRequestStreamClosed); + } + return requestOut; + } + + @Override + public InputStream responseBody() throws IOException { + // Ensure we have response headers first + if (!responseHeadersReceived) { + readResponseHeaders(); + } + + if (responseIn == null) { + responseIn = new DelegatedClosingInputStream(new H2DataInputStream(this), this::onResponseStreamClosed); + } + return responseIn; + } + + private void onRequestStreamClosed() throws IOException { + if (closedStreamCount.incrementAndGet() == 2) { + close(); + } + } + + private void onResponseStreamClosed() throws IOException { + if (closedStreamCount.incrementAndGet() == 2) { + close(); + } + } + + @Override + public HttpHeaders responseHeaders() throws IOException { + if (!responseHeadersReceived) { + readResponseHeaders(); + } + return responseHeaders; + } + + @Override + public int responseStatusCode() throws IOException { + if (!responseHeadersReceived) { + readResponseHeaders(); + } + return statusCode; + } + + @Override + public HttpVersion responseVersion() { + return HttpVersion.HTTP_2; + } + + @Override + public boolean supportsBidirectionalStreaming() { + return true; + } + + @Override + public void close() { + if (!closed.compareAndSet(false, true)) { + return; + } + + // Close request output if not already closed + if (requestOut != null && !endStreamSent) { + try { + requestOut.close(); + } catch (IOException ignored) {} + } + + // If response not fully received and stream was started, queue RST_STREAM + if (!endStreamReceived && streamId > 0 && streamState != StreamState.CLOSED) { + try { + connection.queueRst(streamId, ERROR_CANCEL); + } catch (IOException ignored) { + // Best-effort cleanup. If queue is full, stream is closing anyway. + } + // Signal end to any waiting consumers only if stream didn't end naturally + eventQueue.add(StreamEvent.DataChunk.END); + } + + // Mark stream as closed + streamState = StreamState.CLOSED; + + // Unregister from connection (only if stream was registered) + if (streamId > 0) { + connection.unregisterStream(streamId); + } + } + + /** + * Poll for the next event from the event queue. + * + *

This is a simple blocking poll with timeout. Error events are handled + * inline by callers to avoid double pattern matching on the hot path. + * + * @return the next event (may be an error event) + * @throws SocketTimeoutException if read timeout expires + * @throws IOException if interrupted + */ + private StreamEvent pollEvent() throws IOException { + try { + StreamEvent event = eventQueue.poll(readTimeoutMs, TimeUnit.MILLISECONDS); + if (event == null) { + throw new SocketTimeoutException("Read timed out after " + readTimeoutMs + "ms waiting for response"); + } + return event; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Interrupted waiting for stream event", e); + } + } + + /** + * Read and parse response headers from the event queue. + * + *

Headers are decoded by the connection's reader thread to ensure + * HPACK dynamic table consistency across all streams. + */ + private void readResponseHeaders() throws IOException { + while (!responseHeadersReceived) { + switch (pollEvent()) { + case StreamEvent.Headers h -> handleHeadersEvent(h); + case StreamEvent.DataChunk chunk -> throw new H2Exception(ERROR_PROTOCOL_ERROR, + streamId, + "Received DATA before response headers"); + case StreamEvent.StreamError se -> throw new IOException( + "Stream error before response headers", + se.cause()); + case StreamEvent.ConnectionError ce -> throw new IOException( + "Connection error before response headers", + ce.cause()); + } + } + } + + /** + * Handle a headers event during response reading. + */ + private void handleHeadersEvent(StreamEvent.Headers event) throws IOException { + // Validate stream state per RFC 9113 Section 5.1 + if (streamState == StreamState.CLOSED) { + throw new H2Exception(ERROR_STREAM_CLOSED, streamId, "Received HEADERS on closed stream"); + } + + List fields = event.fields(); + boolean isEndStream = event.endStream(); + + if (!responseHeadersReceived) { + // This is either informational (1xx) or final response headers + if (fields.isEmpty()) { + throw new IOException("Empty HEADERS frame received"); + } + processResponseHeaders(fields, isEndStream); + } else { + // We already have final response headers - this must be trailers + if (!isEndStream) { + // RFC 9113 Section 8.1: Trailers MUST have END_STREAM. + throw new H2Exception(ERROR_PROTOCOL_ERROR, streamId, "Trailer HEADERS frame missing END_STREAM"); + } + if (!fields.isEmpty()) { + processTrailers(fields); + } + } + + if (isEndStream) { + endStreamReceived = true; + updateStreamStateOnEndStream(); + validateContentLength(); + } + } + + /** + * Update stream state when END_STREAM is received. + */ + private void updateStreamStateOnEndStream() { + if (streamState == StreamState.OPEN) { + streamState = StreamState.HALF_CLOSED_REMOTE; + } else if (streamState == StreamState.HALF_CLOSED_LOCAL) { + streamState = StreamState.CLOSED; + } + } + + /** + * Process response headers with full RFC 9113 validation. + * + *

Headers are already decoded by the reader thread to maintain HPACK state. + * + * @param fields the decoded header fields + * @param isEndStream whether this HEADERS frame has END_STREAM flag + */ + private void processResponseHeaders(List fields, boolean isEndStream) throws IOException { + ModifiableHttpHeaders headers = HttpHeaders.ofModifiable(); + int parsedStatusCode = -1; + boolean seenRegularHeader = false; + long contentLength = -1; + + for (HpackDecoder.HeaderField field : fields) { + String name = field.name(); + String value = field.value(); + + if (name.startsWith(":")) { + // Pseudo-header validation per RFC 9113 Section 8.3 + // RFC 9113 Section 8.3: All pseudo-headers MUST appear before regular headers + if (seenRegularHeader) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + streamId, + "Pseudo-header '" + name + "' appears after regular header (RFC 9113 Section 8.3)"); + } + + if (name.equals(PSEUDO_STATUS)) { + // RFC 9113 Section 8.3.2: Response MUST have exactly one :status + if (parsedStatusCode != -1) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + streamId, + "Multiple :status pseudo-headers in response"); + } + try { + parsedStatusCode = Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new IOException("Invalid :status value: " + value); + } + } else if (REQUEST_PSEUDO_HEADERS.contains(name)) { + // RFC 9113 Section 8.3: Request pseudo-headers are NOT allowed in responses + throw new H2Exception(ERROR_PROTOCOL_ERROR, + streamId, + "Request pseudo-header '" + name + "' not allowed in response (RFC 9113 Section 8.3)"); + } else { + // Unknown pseudo-header - RFC 9113 says endpoints MUST treat as malformed + throw new H2Exception(ERROR_PROTOCOL_ERROR, + streamId, + "Unknown pseudo-header '" + name + "' in response"); + } + } else { + // Regular header + seenRegularHeader = true; + + // Track Content-Length for validation per RFC 9113 Section 8.1.1 + if ("content-length".equals(name)) { + try { + long parsedLength = Long.parseLong(value); + if (contentLength != -1 && contentLength != parsedLength) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + streamId, + "Multiple different Content-Length values"); + } + contentLength = parsedLength; + } catch (NumberFormatException e) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + streamId, + "Invalid Content-Length value: " + value); + } + } + + headers.addHeader(name, value); + } + } + + if (parsedStatusCode == -1) { + throw new IOException("Response missing :status pseudo-header"); + } + + // Check if this is an informational (1xx) response + if (parsedStatusCode >= 100 && parsedStatusCode < 200) { + // RFC 9113 Section 8.1.1: Informational responses are interim, continue waiting + // 1xx responses MUST NOT have END_STREAM (except 101 which is not allowed in HTTP/2) + if (isEndStream) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + streamId, + "Informational response (1xx) must not have END_STREAM"); + } + informationalResponses.add(new InformationalResponse(parsedStatusCode, headers)); + // Don't mark responseHeadersReceived - wait for final response + return; + } + + // This is the final response (2xx-5xx) + this.statusCode = parsedStatusCode; + this.responseHeaders = headers; + this.expectedContentLength = contentLength; + this.responseHeadersReceived = true; + } + + /** + * Process trailer headers per RFC 9113 Section 8.1. + * + *

Trailers are HEADERS sent after DATA with END_STREAM. They MUST NOT + * contain pseudo-headers. + * + *

Headers are already decoded by the reader thread to maintain HPACK state. + * + * @param fields the pre-decoded header fields + */ + private void processTrailers(List fields) throws IOException { + ModifiableHttpHeaders trailers = HttpHeaders.ofModifiable(); + for (HpackDecoder.HeaderField field : fields) { + String name = field.name(); + // RFC 9113 Section 8.1: Trailers MUST NOT contain pseudo-headers + if (name.startsWith(":")) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + streamId, + "Trailer contains pseudo-header '" + name + "' (RFC 9113 Section 8.1)"); + } + trailers.addHeader(name, field.value()); + } + + this.trailerHeaders = trailers; + } + + /** + * Validate Content-Length matches actual data received. + * RFC 9113 Section 8.1.1. + */ + private void validateContentLength() throws IOException { + if (expectedContentLength >= 0 && receivedContentLength != expectedContentLength) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + streamId, + "Content-Length mismatch: expected " + expectedContentLength + + " bytes, received " + receivedContentLength + " bytes (RFC 9113 Section 8.1.1)"); + } + } + + /** + * Update stream send window from WINDOW_UPDATE frame. + * + *

Called by the connection's reader thread when a stream-level + * WINDOW_UPDATE is received. This is separate from the event queue + * because WINDOW_UPDATE affects the request send path, not response reading. + * + * @param increment the window size increment + * @throws H2Exception if the increment causes overflow + */ + void updateStreamSendWindow(int increment) throws H2Exception { + flowControlLock.lock(); + try { + streamSendWindow += increment; + // Check for overflow per RFC 9113 (wrap-around to negative) + if (streamSendWindow < 0) { + throw new H2Exception(ERROR_FLOW_CONTROL_ERROR, + streamId, + "Stream send window overflow: " + streamSendWindow); + } + windowAvailable.signalAll(); + } finally { + flowControlLock.unlock(); + } + } + + /** + * Write DATA frame for request body with flow control. + * + * @throws SocketTimeoutException if write timeout expires waiting for flow control window + */ + void writeData(byte[] data, int offset, int length, boolean endStream) throws IOException { + while (length > 0) { + int toSend; + + flowControlLock.lock(); + try { + // Wait for flow control window - need BOTH stream AND connection windows to be positive + while (streamSendWindow <= 0 || connection.getConnectionSendWindow() <= 0) { + try { + if (!windowAvailable.await(writeTimeoutMs, TimeUnit.MILLISECONDS)) { + throw new SocketTimeoutException( + "Write timed out after " + writeTimeoutMs + "ms waiting for flow control window"); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Interrupted waiting for flow control window", e); + } + } + + int available = Math.min(streamSendWindow, connection.getConnectionSendWindow()); + available = Math.min(available, connection.getRemoteMaxFrameSize()); + toSend = Math.min(available, length); + + streamSendWindow -= toSend; + } finally { + flowControlLock.unlock(); + } + + boolean isLast = endStream && (toSend == length); + int flags = isLast ? FLAG_END_STREAM : 0; + + // Queue the write - writer thread handles I/O with batching + connection.queueData(streamId, data, offset, toSend, flags); + connection.consumeConnectionSendWindow(toSend); + + offset += toSend; + length -= toSend; + } + + if (endStream) { + endStreamSent = true; + // Update stream state + if (streamState == StreamState.OPEN) { + streamState = StreamState.HALF_CLOSED_LOCAL; + } else if (streamState == StreamState.HALF_CLOSED_REMOTE) { + streamState = StreamState.CLOSED; + } + } + } + + /** + * Send END_STREAM without data. + */ + void sendEndStream() throws IOException { + if (!endStreamSent) { + connection.queueData(streamId, EMPTY_DATA, 0, 0, FLAG_END_STREAM); + endStreamSent = true; + // Update stream state + if (streamState == StreamState.OPEN) { + streamState = StreamState.HALF_CLOSED_LOCAL; + } else if (streamState == StreamState.HALF_CLOSED_REMOTE) { + streamState = StreamState.CLOSED; + } + } + } + + /** + * Read next data chunk from response. + * + *

Data chunks arrive via the unified event queue from the connection's + * reader thread. This method also handles trailers (HEADERS with END_STREAM + * after DATA) and stream-level flow control. + */ + StreamEvent.DataChunk readDataChunk() throws IOException { + // If we haven't received headers yet, read them first + if (!responseHeadersReceived) { + readResponseHeaders(); + } + + // If we already know stream has ended, return immediately + if (endStreamReceived) { + return StreamEvent.DataChunk.END; + } + + while (true) { + StreamEvent event = pollEvent(); + + switch (event) { + case StreamEvent.DataChunk chunk -> { + // Track received content length for validation + if (chunk.data() != null && chunk.length() > 0) { + receivedContentLength += chunk.length(); + + // Update stream-level flow control (only if stream is not ending) + // Connection-level flow control is handled by the connection + if (!chunk.endStream()) { + updateStreamRecvWindow(chunk.length()); + } + } + + if (chunk.endStream()) { + endStreamReceived = true; + updateStreamStateOnEndStream(); + validateContentLength(); + } + + return chunk; + } + + case StreamEvent.Headers h -> { + // Headers after data = trailers (must have END_STREAM) + if (!h.endStream()) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + streamId, + "Trailer HEADERS frame must have END_STREAM flag"); + } + if (!h.fields().isEmpty()) { + processTrailers(h.fields()); + } + endStreamReceived = true; + updateStreamStateOnEndStream(); + validateContentLength(); + return StreamEvent.DataChunk.END; + } + + case StreamEvent.StreamError se -> throw new IOException( + "Stream error while reading data", + se.cause()); + + case StreamEvent.ConnectionError ce -> throw new IOException( + "Connection error while reading data", + ce.cause()); + } + } + } + + /** + * Update stream receive window after consuming data. + * + *

Sends WINDOW_UPDATE when the window gets below half of the initial size. + * + * @throws IOException if the write queue is full + */ + private void updateStreamRecvWindow(int bytesConsumed) throws IOException { + streamRecvWindow -= bytesConsumed; + if (streamRecvWindow < DEFAULT_INITIAL_WINDOW_SIZE / 2) { + int increment = DEFAULT_INITIAL_WINDOW_SIZE - streamRecvWindow; + // Queue stream-level WINDOW_UPDATE - writer thread will send it + connection.queueWindowUpdate(streamId, increment); + streamRecvWindow += increment; + } + } + + /** + * Get informational (1xx) responses received before the final response. + * + *

Per RFC 9113 Section 8.1.1, a server may send one or more informational + * responses (status codes 100-199) before the final response. Common examples + * include 100 Continue and 103 Early Hints. + * + * @return list of informational responses (may be empty, never null) + */ + public List informationalResponses() { + return List.copyOf(informationalResponses); + } + + @Override + public HttpHeaders responseTrailerHeaders() { + return trailerHeaders; + } + + /** + * Informational response (1xx) received before the final response. + * + * @param statusCode the informational status code (100-199) + * @param headers the headers from this informational response + */ + public record InformationalResponse(int statusCode, HttpHeaders headers) {} +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java new file mode 100644 index 000000000..14c941434 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java @@ -0,0 +1,729 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_FRAME_SIZE_ERROR; +import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_PROTOCOL_ERROR; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_ACK; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_END_HEADERS; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_END_STREAM; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_PADDED; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_PRIORITY; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_HEADER_SIZE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_CONTINUATION; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_DATA; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_GOAWAY; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_HEADERS; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_PING; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_PRIORITY; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_PUSH_PROMISE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_RST_STREAM; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_SETTINGS; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_WINDOW_UPDATE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.frameTypeName; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +/** + * HTTP/2 frame encoding and decoding. + * + *

HTTP/2 frames have a 9-byte header followed by a variable-length payload: + *

+ * +-----------------------------------------------+
+ * |                 Length (24)                   |
+ * +---------------+---------------+---------------+
+ * |   Type (8)    |   Flags (8)   |
+ * +-+-------------+---------------+-------------------------------+
+ * |R|                 Stream Identifier (31)                      |
+ * +=+=============================================================+
+ * |                   Frame Payload (0...)                      ...
+ * +---------------------------------------------------------------+
+ * 
+ */ +final class H2FrameCodec { + + private final InputStream in; + private final OutputStream out; + private final int maxFrameSize; + + // Separate buffers for reading and writing to avoid concurrent access issues. + // The reader thread uses readHeaderBuf, while writer threads use writeHeaderBuf. + private final byte[] readHeaderBuf = new byte[FRAME_HEADER_SIZE]; + private final byte[] writeHeaderBuf = new byte[FRAME_HEADER_SIZE]; + + // Scratch buffer for control frames to avoid allocation on hot path. + // Covers PING(8), SETTINGS(up to ~10 params = 60), WINDOW_UPDATE(4), RST_STREAM(4), + // PRIORITY(5), and small GOAWAY frames. Larger payloads fall back to allocation. + private static final int CONTROL_FRAME_SCRATCH_SIZE = 64; + private final byte[] controlFrameScratch = new byte[CONTROL_FRAME_SCRATCH_SIZE]; + + // Shared empty array for zero-length payloads + private static final byte[] EMPTY_PAYLOAD = new byte[0]; + + H2FrameCodec(InputStream in, OutputStream out, int maxFrameSize) { + this.in = in; + this.out = out; + this.maxFrameSize = maxFrameSize; + } + + /** + * Read a frame from the input stream. + * + *

Important: For control frames (SETTINGS, PING, WINDOW_UPDATE, RST_STREAM, + * PRIORITY, GOAWAY), the returned Frame's payload may reference a shared scratch buffer. + * The payload is only valid until the next call to {@code readFrame()}. Callers must + * parse control frames immediately before reading the next frame. + * + * @return the frame, or null if EOF + * @throws IOException if reading fails or frame is malformed + */ + Frame readFrame() throws IOException { + // Read 9-byte header + int read = readFully(readHeaderBuf, 0, FRAME_HEADER_SIZE); + if (read < FRAME_HEADER_SIZE) { + if (read == 0) { + return null; // EOF + } + throw new IOException("Incomplete frame header: read " + read + " bytes"); + } + + // Parse header + int length = ((readHeaderBuf[0] & 0xFF) << 16) | ((readHeaderBuf[1] & 0xFF) << 8) | (readHeaderBuf[2] & 0xFF); + int type = readHeaderBuf[3] & 0xFF; + int flags = readHeaderBuf[4] & 0xFF; + int streamId = ((readHeaderBuf[5] & 0x7F) << 24) // Mask off reserved bit + | ((readHeaderBuf[6] & 0xFF) << 16) + | ((readHeaderBuf[7] & 0xFF) << 8) + | (readHeaderBuf[8] & 0xFF); + + // Validate frame size + if (length > maxFrameSize) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, "Frame size " + length + " exceeds " + maxFrameSize); + } + + // Validate stream ID requirements per RFC 9113 + validateStreamId(type, streamId); + + // Validate fixed-size frame payloads per RFC 9113 + validateFrameSize(type, flags, length); + + // Read payload - use scratch buffer for control frames to avoid allocation + byte[] payload; + if (length == 0) { + payload = EMPTY_PAYLOAD; + } else if (isControlFrame(type) && length <= CONTROL_FRAME_SCRATCH_SIZE) { + // Control frames use scratch buffer (valid until next readFrame call) + read = readFully(controlFrameScratch, 0, length); + if (read < length) { + throw new IOException("Incomplete frame payload: expected " + length + ", read " + read); + } + payload = controlFrameScratch; + } else { + // DATA, HEADERS, CONTINUATION, PUSH_PROMISE, or large control frames + payload = new byte[length]; + read = readFully(payload, 0, length); + if (read < length) { + throw new IOException("Incomplete frame payload: expected " + length + ", read " + read); + } + } + + // Handle padding for DATA, HEADERS, and PUSH_PROMISE frames + if ((flags & FLAG_PADDED) != 0 + && (type == FRAME_TYPE_DATA || type == FRAME_TYPE_HEADERS || type == FRAME_TYPE_PUSH_PROMISE)) { + payload = removePadding(payload, type); + length = payload.length; // Update length after stripping padding + flags &= ~FLAG_PADDED; // Clear padded flag after processing + } + + // Handle PRIORITY in HEADERS frame + if (type == FRAME_TYPE_HEADERS && (flags & FLAG_PRIORITY) != 0) { + payload = removePriority(payload); + length = payload.length; // Update length after stripping priority + flags &= ~FLAG_PRIORITY; // Clear priority flag after processing + } + + return new Frame(type, flags, streamId, payload, length); + } + + /** + * Check if frame type is a control frame (processed immediately, payload doesn't escape). + */ + private static boolean isControlFrame(int type) { + return type == FRAME_TYPE_SETTINGS + || type == FRAME_TYPE_PING + || type == FRAME_TYPE_WINDOW_UPDATE + || type == FRAME_TYPE_RST_STREAM + || type == FRAME_TYPE_PRIORITY + || type == FRAME_TYPE_GOAWAY; + } + + /** + * Read a complete header block, handling CONTINUATION frames. + * + *

Per RFC 9113 Section 4.3, a header block must be transmitted as a contiguous + * sequence of frames with no interleaved frames of any other type or from any other stream. + * + * @param initialFrame the initial HEADERS or PUSH_PROMISE frame + * @return the complete header block payload + * @throws IOException if reading fails + */ + byte[] readHeaderBlock(Frame initialFrame) throws IOException { + byte[] initialPayload = initialFrame.payload(); + int initialLength = initialFrame.payloadLength(); + + // For PUSH_PROMISE, strip the 4-byte promised stream ID to get the header block fragment + if (initialFrame.type() == FRAME_TYPE_PUSH_PROMISE && initialPayload != null) { + if (initialLength < 4) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "PUSH_PROMISE frame payload too short for promised stream ID"); + } + int fragmentLength = initialLength - 4; + byte[] fragment = new byte[fragmentLength]; + System.arraycopy(initialPayload, 4, fragment, 0, fragmentLength); + initialPayload = fragment; + initialLength = fragmentLength; + } + + if (initialFrame.hasFlag(FLAG_END_HEADERS)) { + return initialPayload != null ? initialPayload : EMPTY_PAYLOAD; + } + + // Need to read CONTINUATION frames + ByteArrayOutputStream headerBlock = new ByteArrayOutputStream(initialLength); + if (initialPayload != null) { + headerBlock.write(initialPayload); + } + + while (true) { + Frame cont = readFrame(); + if (cont == null) { + throw new IOException("EOF while reading CONTINUATION frames"); + } + + // Per RFC 9113 Section 4.3: header block must be contiguous + // Only CONTINUATION frames for the same stream are allowed + if (cont.type() != FRAME_TYPE_CONTINUATION) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + "Header block interrupted by " + frameTypeName(cont.type()) + + " frame (RFC 9113 Section 4.3 violation)"); + } + + if (cont.streamId() != initialFrame.streamId()) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + "CONTINUATION frame stream ID mismatch: expected " + + initialFrame.streamId() + ", got " + cont.streamId()); + } + + byte[] contPayload = cont.payload(); + if (contPayload != null) { + headerBlock.write(contPayload); + } + + if (cont.hasFlag(FLAG_END_HEADERS)) { + break; + } + } + + return headerBlock.toByteArray(); + } + + private void validateFrameSize(int type, int flags, int length) throws H2Exception { + switch (type) { + case FRAME_TYPE_PING: + // PING frames MUST have exactly 8 bytes payload + if (length != 8) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "PING frame must have 8-byte payload, got " + length); + } + break; + + case FRAME_TYPE_SETTINGS: + // SETTINGS with ACK flag MUST have empty payload + if ((flags & FLAG_ACK) != 0 && length != 0) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "SETTINGS ACK frame must have empty payload, got " + length); + } + // SETTINGS payload must be multiple of 6 (validated in parseSettings) + break; + + case FRAME_TYPE_WINDOW_UPDATE: + // WINDOW_UPDATE frames MUST have exactly 4 bytes payload + if (length != 4) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "WINDOW_UPDATE frame must have 4-byte payload, got " + length); + } + break; + + case FRAME_TYPE_RST_STREAM: + // RST_STREAM frames MUST have exactly 4 bytes payload + if (length != 4) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "RST_STREAM frame must have 4-byte payload, got " + length); + } + break; + + case FRAME_TYPE_PRIORITY: + // PRIORITY frames MUST have exactly 5 bytes payload + if (length != 5) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "PRIORITY frame must have 5-byte payload, got " + length); + } + break; + + case FRAME_TYPE_GOAWAY: + // GOAWAY frames MUST have at least 8 bytes payload + if (length < 8) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "GOAWAY frame must have at least 8-byte payload, got " + length); + } + break; + + case FRAME_TYPE_PUSH_PROMISE: + // PUSH_PROMISE must have at least 4 bytes for the promised stream ID + // (plus 1 byte for pad length if PADDED flag is set) + int minLength = (flags & FLAG_PADDED) != 0 ? 5 : 4; + if (length < minLength) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "PUSH_PROMISE frame must have at least " + minLength + "-byte payload, got " + length); + } + break; + + default: + // Other frame types have variable-length payloads + break; + } + } + + /** + * Validate stream ID requirements per RFC 9113. + */ + private void validateStreamId(int type, int streamId) throws H2Exception { + switch (type) { + case FRAME_TYPE_DATA: + case FRAME_TYPE_HEADERS: + case FRAME_TYPE_PRIORITY: + case FRAME_TYPE_RST_STREAM: + case FRAME_TYPE_CONTINUATION: + // These frames MUST be associated with a stream + if (streamId == 0) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + frameTypeName(type) + " frame must have non-zero stream ID"); + } + break; + + case FRAME_TYPE_SETTINGS: + case FRAME_TYPE_PING: + case FRAME_TYPE_GOAWAY: + // These frames MUST NOT be associated with a stream + if (streamId != 0) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + frameTypeName(type) + " frame must have stream ID 0, got " + streamId); + } + break; + + case FRAME_TYPE_WINDOW_UPDATE: + // Can be on connection (0) or stream (non-zero) + break; + + case FRAME_TYPE_PUSH_PROMISE: + // Must be on a stream + if (streamId == 0) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + "PUSH_PROMISE frame must have non-zero stream ID"); + } + break; + + default: + // Unknown frame types - ignore per RFC 9113 + break; + } + } + + /** + * Remove padding from a padded frame payload. + */ + private byte[] removePadding(byte[] payload, int frameType) throws H2Exception { + if (payload.length < 1) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "Padded " + frameTypeName(frameType) + " frame too short"); + } + + int padLength = payload[0] & 0xFF; + if (padLength >= payload.length) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + "Pad length " + padLength + " exceeds payload length " + payload.length); + } + + int dataLength = payload.length - 1 - padLength; + byte[] data = new byte[dataLength]; + System.arraycopy(payload, 1, data, 0, dataLength); + return data; + } + + /** + * Remove PRIORITY fields from HEADERS frame payload. + */ + private byte[] removePriority(byte[] payload) throws H2Exception { + // PRIORITY adds 5 bytes: 4-byte stream dependency + 1-byte weight + if (payload.length < 5) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "HEADERS frame with PRIORITY flag too short"); + } + + byte[] data = new byte[payload.length - 5]; + System.arraycopy(payload, 5, data, 0, data.length); + return data; + } + + /** + * Write a frame to the output stream. + * + * @param type frame type + * @param flags frame flags + * @param streamId stream identifier + * @param payload frame payload (may be null or empty) + * @throws IOException if writing fails + */ + void writeFrame(int type, int flags, int streamId, byte[] payload) throws IOException { + writeFrame(type, flags, streamId, payload, 0, payload != null ? payload.length : 0); + } + + /** + * Write a frame to the output stream. + * + *

This method is NOT synchronized. Callers must ensure exclusive access + * to the output stream (e.g., via H2Connection's writer thread). + * + * @param type frame type + * @param flags frame flags + * @param streamId stream identifier + * @param payload frame payload buffer + * @param offset offset in payload buffer + * @param length number of bytes to write from payload + * @throws IOException if writing fails + */ + void writeFrame( + int type, + int flags, + int streamId, + byte[] payload, + int offset, + int length + ) throws IOException { + // Validate stream ID is a valid 31-bit unsigned value + if (streamId < 0) { + throw new IllegalArgumentException("Invalid stream ID: " + streamId); + } + + if (length > maxFrameSize) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "Frame payload size " + length + " exceeds maximum " + maxFrameSize); + } + + // Write header (using writeHeaderBuf - caller must ensure exclusive access) + writeHeaderBuf[0] = (byte) ((length >> 16) & 0xFF); + writeHeaderBuf[1] = (byte) ((length >> 8) & 0xFF); + writeHeaderBuf[2] = (byte) (length & 0xFF); + writeHeaderBuf[3] = (byte) type; + writeHeaderBuf[4] = (byte) flags; + writeHeaderBuf[5] = (byte) ((streamId >> 24) & 0x7F); // Clear reserved bit + writeHeaderBuf[6] = (byte) ((streamId >> 16) & 0xFF); + writeHeaderBuf[7] = (byte) ((streamId >> 8) & 0xFF); + writeHeaderBuf[8] = (byte) (streamId & 0xFF); + + out.write(writeHeaderBuf); + + // Write payload + if (length > 0 && payload != null) { + out.write(payload, offset, length); + } + } + + /** + * Write HEADERS frame, splitting into CONTINUATION frames if needed. + */ + void writeHeaders(int streamId, byte[] headerBlock, int offset, int length, boolean endStream) throws IOException { + if (length <= maxFrameSize) { + // Fits in single frame + int flags = FLAG_END_HEADERS; + if (endStream) { + flags |= FLAG_END_STREAM; + } + writeFrame(FRAME_TYPE_HEADERS, flags, streamId, headerBlock, offset, length); + } else { + // Need to split across HEADERS + CONTINUATION frames + int pos = offset; + int end = offset + length; + + // First frame: HEADERS (no END_HEADERS flag) + int firstFlags = endStream ? FLAG_END_STREAM : 0; + writeFrame(FRAME_TYPE_HEADERS, firstFlags, streamId, headerBlock, pos, maxFrameSize); + pos += maxFrameSize; + + // Middle frames: CONTINUATION (no END_HEADERS flag) + while (pos + maxFrameSize < end) { + writeFrame(FRAME_TYPE_CONTINUATION, 0, streamId, headerBlock, pos, maxFrameSize); + pos += maxFrameSize; + } + + // Last frame: CONTINUATION with END_HEADERS + int remaining = end - pos; + writeFrame(FRAME_TYPE_CONTINUATION, FLAG_END_HEADERS, streamId, headerBlock, pos, remaining); + } + } + + /** + * Write SETTINGS frame. + */ + void writeSettings(int... settings) throws IOException { + if (settings.length % 2 != 0) { + throw new IllegalArgumentException("Settings must be id-value pairs"); + } + + // Each pair is 2 ints (id + value) and encodes to 6 bytes (2 + 4) + byte[] payload = new byte[settings.length * 3]; + int pos = 0; + for (int i = 0; i < settings.length; i += 2) { + int id = settings[i]; + int value = settings[i + 1]; + payload[pos++] = (byte) ((id >> 8) & 0xFF); + payload[pos++] = (byte) (id & 0xFF); + payload[pos++] = (byte) ((value >> 24) & 0xFF); + payload[pos++] = (byte) ((value >> 16) & 0xFF); + payload[pos++] = (byte) ((value >> 8) & 0xFF); + payload[pos++] = (byte) (value & 0xFF); + } + + writeFrame(FRAME_TYPE_SETTINGS, 0, 0, payload); + } + + /** + * Write SETTINGS acknowledgment. + */ + void writeSettingsAck() throws IOException { + writeFrame(FRAME_TYPE_SETTINGS, FLAG_ACK, 0, null); + } + + /** + * Write GOAWAY frame. + */ + void writeGoaway(int lastStreamId, int errorCode, String debugData) throws IOException { + byte[] debug = debugData != null ? debugData.getBytes(StandardCharsets.UTF_8) : EMPTY_PAYLOAD; + byte[] payload = new byte[8 + debug.length]; + + payload[0] = (byte) ((lastStreamId >> 24) & 0x7F); + payload[1] = (byte) ((lastStreamId >> 16) & 0xFF); + payload[2] = (byte) ((lastStreamId >> 8) & 0xFF); + payload[3] = (byte) (lastStreamId & 0xFF); + payload[4] = (byte) ((errorCode >> 24) & 0xFF); + payload[5] = (byte) ((errorCode >> 16) & 0xFF); + payload[6] = (byte) ((errorCode >> 8) & 0xFF); + payload[7] = (byte) (errorCode & 0xFF); + + System.arraycopy(debug, 0, payload, 8, debug.length); + + writeFrame(FRAME_TYPE_GOAWAY, 0, 0, payload); + } + + /** + * Write WINDOW_UPDATE frame. + * Uses scratch buffer - caller must have exclusive access (writer thread). + */ + void writeWindowUpdate(int streamId, int windowSizeIncrement) throws IOException { + if (windowSizeIncrement <= 0) { + throw new IllegalArgumentException("Invalid window size increment: " + windowSizeIncrement); + } + + // Use scratch buffer to avoid allocation + controlFrameScratch[0] = (byte) ((windowSizeIncrement >> 24) & 0x7F); + controlFrameScratch[1] = (byte) ((windowSizeIncrement >> 16) & 0xFF); + controlFrameScratch[2] = (byte) ((windowSizeIncrement >> 8) & 0xFF); + controlFrameScratch[3] = (byte) (windowSizeIncrement & 0xFF); + writeFrame(FRAME_TYPE_WINDOW_UPDATE, 0, streamId, controlFrameScratch, 0, 4); + } + + /** + * Write RST_STREAM frame. + * Uses scratch buffer - caller must have exclusive access (writer thread). + */ + void writeRstStream(int streamId, int errorCode) throws IOException { + // Use scratch buffer to avoid allocation + controlFrameScratch[0] = (byte) ((errorCode >> 24) & 0xFF); + controlFrameScratch[1] = (byte) ((errorCode >> 16) & 0xFF); + controlFrameScratch[2] = (byte) ((errorCode >> 8) & 0xFF); + controlFrameScratch[3] = (byte) (errorCode & 0xFF); + writeFrame(FRAME_TYPE_RST_STREAM, 0, streamId, controlFrameScratch, 0, 4); + } + + /** + * Flush the output stream. + * + *

Caller must ensure exclusive access to the output stream. + */ + void flush() throws IOException { + out.flush(); + } + + private int readFully(byte[] buf, int off, int len) throws IOException { + int total = 0; + while (total < len) { + int n = in.read(buf, off + total, len - total); + if (n < 0) { + break; + } + total += n; + } + return total; + } + + /** + * Represents an HTTP/2 frame. + * + *

Note: For control frames, the payload array may be a shared scratch buffer + * that is larger than the actual payload. Always use {@link #payloadLength()} to get + * the actual payload size, not {@code payload.length}. + * + * @param type frame type + * @param flags frame flags + * @param streamId stream identifier + * @param payload payload bytes (may be shared scratch buffer for control frames) + * @param length actual payload length (may be less than payload.length for scratch buffer) + */ + record Frame(int type, int flags, int streamId, byte[] payload, int length) { + + boolean hasFlag(int flag) { + return (flags & flag) != 0; + } + + int payloadLength() { + return length; + } + + /** + * Parse SETTINGS frame payload. + * + * @return array of {id, value} pairs + * @throws H2Exception if frame is invalid + */ + int[] parseSettings() throws H2Exception { + if (type != FRAME_TYPE_SETTINGS) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + "Expected SETTINGS frame, got " + frameTypeName(type)); + } + if (payload == null || length == 0) { + return new int[0]; + } + + // SETTINGS payload MUST be a multiple of 6 bytes (RFC 9113 Section 6.5) + if (length % 6 != 0) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "SETTINGS frame payload length " + length + " is not a multiple of 6"); + } + + int count = length / 6; + int[] settings = new int[count * 2]; + int pos = 0; + for (int i = 0; i < count; i++) { + int id = ((payload[pos] & 0xFF) << 8) | (payload[pos + 1] & 0xFF); + int value = ((payload[pos + 2] & 0xFF) << 24) + | ((payload[pos + 3] & 0xFF) << 16) + | ((payload[pos + 4] & 0xFF) << 8) + | (payload[pos + 5] & 0xFF); + settings[i * 2] = id; + settings[i * 2 + 1] = value; + pos += 6; + } + return settings; + } + + /** + * Parse GOAWAY frame payload. + * + * @return {lastStreamId, errorCode} + * @throws H2Exception if frame is invalid + */ + int[] parseGoaway() throws H2Exception { + if (type != FRAME_TYPE_GOAWAY) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, "Expected GOAWAY frame, got " + frameTypeName(type)); + } else if (payload == null || length < 8) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, "GOAWAY frame payload too short: " + length); + } + + int lastStreamId = ((payload[0] & 0x7F) << 24) + | ((payload[1] & 0xFF) << 16) + | ((payload[2] & 0xFF) << 8) + | (payload[3] & 0xFF); + int errorCode = ((payload[4] & 0xFF) << 24) + | ((payload[5] & 0xFF) << 16) + | ((payload[6] & 0xFF) << 8) + | (payload[7] & 0xFF); + return new int[] {lastStreamId, errorCode}; + } + + /** + * Parse WINDOW_UPDATE frame payload. + * + * @return window size increment + * @throws H2Exception if frame is invalid or increment is zero + */ + int parseWindowUpdate() throws H2Exception { + if (type != FRAME_TYPE_WINDOW_UPDATE) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + "Expected WINDOW_UPDATE frame, got " + frameTypeName(type)); + } + if (payload == null || length != 4) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "WINDOW_UPDATE frame must have 4-byte payload, got " + length); + } + + int increment = ((payload[0] & 0x7F) << 24) + | ((payload[1] & 0xFF) << 16) + | ((payload[2] & 0xFF) << 8) + | (payload[3] & 0xFF); + + if (increment == 0) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, "WINDOW_UPDATE increment must be non-zero"); + } + + return increment; + } + + /** + * Parse RST_STREAM frame payload. + * + * @return error code + * @throws H2Exception if frame is invalid + */ + int parseRstStream() throws H2Exception { + if (type != FRAME_TYPE_RST_STREAM) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + "Expected RST_STREAM frame, got " + frameTypeName(type)); + } + if (payload == null || length != 4) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "RST_STREAM frame must have 4-byte payload, got " + length); + } + + return ((payload[0] & 0xFF) << 24) + | ((payload[1] & 0xFF) << 16) + | ((payload[2] & 0xFF) << 8) + | (payload[3] & 0xFF); + } + + @Override + public String toString() { + return String.format("Frame{type=%s, flags=0x%02x, streamId=%d, length=%d}", + frameTypeName(type), + flags, + streamId, + length); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamWriter.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamWriter.java new file mode 100644 index 000000000..3d0176fbf --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamWriter.java @@ -0,0 +1,520 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_REFUSED_STREAM; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_ACK; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_DATA; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_PING; +import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_AUTHORITY; +import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_METHOD; +import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_PATH; +import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_SCHEME; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.client.h2.hpack.HpackEncoder; +import software.amazon.smithy.java.io.ByteBufferOutputStream; + +/** + * Combined HPACK encoder and frame writer for HTTP/2 connections. + * + *

This class handles both header encoding AND frame writing in a single thread, + * eliminating the handoff overhead between separate encoder and writer threads. + * All socket writes are serialized through this thread. + * + *

Thread Model

+ *
    + *
  • Caller threads submit work items to the queue
  • + *
  • Single thread processes requests: encode (if needed) → write → flush
  • + *
  • Batching: drains queue, processes all, flushes once
  • + *
+ * + *

Ordering Guarantees

+ *
    + *
  • Stream IDs are allocated in submission order
  • + *
  • HPACK dynamic table updates happen in wire order
  • + *
  • Frames are written in submission order
  • + *
+ */ +final class H2StreamWriter implements AutoCloseable { + + /** + * Work items that can be submitted to the encoder/writer thread. + */ + sealed interface WorkItem { + /** Encode headers and write HEADERS frame for a new stream. */ + record EncodeHeaders( + HttpRequest request, + H2Exchange exchange, + boolean endStream, + CompletableFuture streamIdFuture, + CompletableFuture writeComplete) implements WorkItem {} + + /** Write a DATA frame. */ + record WriteData( + int streamId, + byte[] data, + int offset, + int length, + int flags, + CompletableFuture completion) implements WorkItem {} + + /** Write a RST_STREAM frame. */ + record WriteRst( + int streamId, + int errorCode, + CompletableFuture completion) implements WorkItem {} + + /** Write a GOAWAY frame (fire-and-forget). */ + record WriteGoaway(int lastStreamId, int errorCode, String debugData) implements WorkItem {} + + /** Write a WINDOW_UPDATE frame (fire-and-forget). */ + record WriteWindowUpdate(int streamId, int increment) implements WorkItem {} + + /** Write a SETTINGS ACK frame (fire-and-forget). */ + record WriteSettingsAck() implements WorkItem {} + + /** Write a PING frame (fire-and-forget). */ + record WritePing(byte[] payload, boolean ack) implements WorkItem {} + + /** Shutdown marker. */ + record Shutdown() implements WorkItem {} + } + + /** + * Interface for encoder to manage streams on the connection. + */ + interface StreamManager { + boolean tryReserveStream(); + + void releaseStreamSlot(); + + void registerStream(int streamId, H2Exchange exchange); + + void unregisterStream(int streamId); + + void setLastStreamId(int streamId); + + boolean isAcceptingStreams(); + + int getRemoteMaxHeaderListSize(); + } + + // Headers that must not be sent over HTTP/2 (connection-specific) + private static final Set CONNECTION_HEADERS = Set.of( + "connection", + "keep-alive", + "proxy-connection", + "transfer-encoding", + "upgrade", + "host"); + + // Headers that should not be indexed in HPACK (contain sensitive data) + private static final Set SENSITIVE_HEADERS = Set.of( + "authorization", + "cookie", + "proxy-authorization", + "set-cookie"); + + private static final int QUEUE_CAPACITY = 1024; + + private final StreamManager streamManager; + private final H2FrameCodec frameCodec; + private final BlockingQueue workQueue; + private final Thread workerThread; + + // HPACK encoder state (only accessed by worker thread - no synchronization needed) + private final HpackEncoder hpackEncoder; + private final ByteBufferOutputStream headerEncodeBuffer; + + // Stream ID allocation (only accessed by worker thread) + private final AtomicInteger nextStreamId = new AtomicInteger(1); // Client uses odd IDs + + // Pending table size update (set by connection thread, read by worker thread) + private volatile int pendingTableSizeUpdate = -1; + + private volatile boolean running = true; + private volatile boolean accepting = true; + + /** + * Create a new combined encoder/writer. + * + * @param streamManager the stream manager for connection interaction + * @param frameCodec the frame codec for writing to socket + * @param initialTableSize initial HPACK dynamic table size + * @param threadName name for the worker thread + */ + H2StreamWriter(StreamManager streamManager, H2FrameCodec frameCodec, int initialTableSize, String threadName) { + this.streamManager = streamManager; + this.frameCodec = frameCodec; + this.workQueue = new LinkedBlockingQueue<>(QUEUE_CAPACITY); + this.hpackEncoder = new HpackEncoder(initialTableSize); + this.headerEncodeBuffer = new ByteBufferOutputStream(512); + this.workerThread = Thread.ofVirtual().name(threadName).start(this::workerLoop); + } + + /** + * Submit a work item with timeout (blocking if queue is full). + */ + boolean submitWork(WorkItem item, long timeoutMs) { + if (!accepting) { + return false; + } + try { + return workQueue.offer(item, timeoutMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + + /** + * Submit a control frame (fire-and-forget, non-blocking). + */ + boolean submitControlFrame(WorkItem item) { + if (!accepting) { + return false; + } + return workQueue.offer(item); + } + + /** + * Update the HPACK encoder's max table size. + */ + void setMaxTableSize(int newSize) { + this.pendingTableSizeUpdate = newSize; + } + + /** + * Get the current stream ID counter value. + */ + int getNextStreamId() { + return nextStreamId.get(); + } + + /** + * Main worker loop - processes work items with batching. + */ + private void workerLoop() { + var batch = new ArrayList(64); + + try { + while (running) { + // Block for the first request + WorkItem item = workQueue.take(); + + if (item instanceof WorkItem.Shutdown) { + break; + } + + batch.add(item); + + // Drain opportunistically (non-blocking) for batching + while ((item = workQueue.poll()) != null) { + if (item instanceof WorkItem.Shutdown) { + processBatch(batch); + return; + } + batch.add(item); + } + + processBatch(batch); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + failRemainingRequests(); + } + + /** + * Process a batch of work items with a single flush at the end. + */ + private void processBatch(ArrayList batch) { + if (batch.isEmpty()) { + return; + } + + try { + // Process all items (encode if needed, write frames) + for (WorkItem item : batch) { + processItem(item); + } + + // Single flush for entire batch + frameCodec.flush(); + + // Complete futures for successful writes + for (WorkItem item : batch) { + completeItem(item, null); + } + } catch (IOException e) { + // Fail all items in batch + for (WorkItem item : batch) { + completeItem(item, e); + } + } finally { + batch.clear(); + } + } + + /** + * Process a single work item. + */ + private void processItem(WorkItem item) throws IOException { + switch (item) { + case WorkItem.EncodeHeaders h -> processEncodeHeaders(h); + case WorkItem.WriteData d -> + frameCodec.writeFrame(FRAME_TYPE_DATA, d.flags(), d.streamId(), d.data(), d.offset(), d.length()); + case WorkItem.WriteRst r -> + frameCodec.writeRstStream(r.streamId(), r.errorCode()); + case WorkItem.WriteGoaway g -> + frameCodec.writeGoaway(g.lastStreamId(), g.errorCode(), g.debugData()); + case WorkItem.WriteWindowUpdate w -> + frameCodec.writeWindowUpdate(w.streamId(), w.increment()); + case WorkItem.WriteSettingsAck s -> + frameCodec.writeSettingsAck(); + case WorkItem.WritePing p -> + frameCodec.writeFrame(FRAME_TYPE_PING, p.ack() ? FLAG_ACK : 0, 0, p.payload()); + case WorkItem.Shutdown s -> { + // handled by caller + } + } + } + + /** + * Process an encode headers request: allocate stream, encode HPACK, write frame. + */ + private void processEncodeHeaders(WorkItem.EncodeHeaders req) throws IOException { + int streamId = -1; + boolean slotReserved = false; + boolean streamRegistered = false; + + try { + if (!streamManager.isAcceptingStreams()) { + req.streamIdFuture() + .completeExceptionally( + new IOException("Connection is not accepting new streams")); + return; + } + + if (!streamManager.tryReserveStream()) { + req.streamIdFuture() + .completeExceptionally( + new IOException("Connection at max concurrent streams")); + return; + } + slotReserved = true; + + streamId = nextStreamId.getAndAdd(2); + if (streamId < 0) { + req.streamIdFuture() + .completeExceptionally( + new H2Exception(ERROR_REFUSED_STREAM, "Stream ID space exhausted")); + streamManager.releaseStreamSlot(); + return; + } + + streamManager.setLastStreamId(streamId); + req.exchange().setStreamId(streamId); + streamManager.registerStream(streamId, req.exchange()); + streamRegistered = true; + req.exchange().onHeadersEncoded(req.endStream()); + + int tableUpdate = pendingTableSizeUpdate; + if (tableUpdate >= 0) { + hpackEncoder.setMaxTableSize(tableUpdate); + pendingTableSizeUpdate = -1; + } + + byte[] headerBlock = encodeHeaders(req.request()); + + // Write directly - no intermediate queue! + frameCodec.writeHeaders(streamId, headerBlock, 0, headerBlock.length, req.endStream()); + + req.streamIdFuture().complete(streamId); + + } catch (Exception e) { + if (streamRegistered) { + // unregisterStream handles both map removal AND count decrement + streamManager.unregisterStream(streamId); + } else if (slotReserved) { + // Slot reserved but stream not registered - just release the slot + streamManager.releaseStreamSlot(); + } + if (e instanceof IOException || e instanceof H2Exception) { + req.streamIdFuture().completeExceptionally(e); + } else { + req.streamIdFuture().completeExceptionally(new IOException("Encoding failed", e)); + } + } + } + + /** + * Complete a work item's future. + */ + private void completeItem(WorkItem item, IOException error) { + CompletableFuture completion = switch (item) { + case WorkItem.EncodeHeaders h -> h.writeComplete(); + case WorkItem.WriteData d -> d.completion(); + case WorkItem.WriteRst r -> r.completion(); + case WorkItem.WriteGoaway g -> null; + case WorkItem.WriteWindowUpdate w -> null; + case WorkItem.WriteSettingsAck s -> null; + case WorkItem.WritePing p -> null; + case WorkItem.Shutdown s -> null; + }; + if (completion != null) { + if (error == null) { + completion.complete(null); + } else { + completion.completeExceptionally(error); + } + } + } + + /** + * Encode headers using HPACK. + */ + private byte[] encodeHeaders(HttpRequest request) throws IOException { + headerEncodeBuffer.reset(); + hpackEncoder.beginHeaderBlock(headerEncodeBuffer); + + long headerListSize = 0; + String method = request.method(); + boolean isConnect = "CONNECT".equalsIgnoreCase(method); + + String authority = getAuthority(request); + String scheme = isConnect ? null : request.uri().getScheme(); + String path = isConnect ? null : getPath(request); + + hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_METHOD, method, false); + headerListSize += PSEUDO_METHOD.length() + method.length() + 32; + + if (!isConnect) { + hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_SCHEME, scheme, false); + headerListSize += PSEUDO_SCHEME.length() + (scheme != null ? scheme.length() : 0) + 32; + } + + hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_AUTHORITY, authority, false); + headerListSize += PSEUDO_AUTHORITY.length() + authority.length() + 32; + + if (!isConnect) { + hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_PATH, path, false); + headerListSize += PSEUDO_PATH.length() + path.length() + 32; + } + + for (var entry : request.headers()) { + String name = entry.getKey(); + if (CONNECTION_HEADERS.contains(name)) { + continue; + } + boolean isTe = "te".equals(name); + boolean sensitive = SENSITIVE_HEADERS.contains(name); + for (String value : entry.getValue()) { + if (isTe && !"trailers".equalsIgnoreCase(value)) { + continue; + } + hpackEncoder.encodeHeader(headerEncodeBuffer, name, value, sensitive); + headerListSize += name.length() + value.length() + 32; + } + } + + int maxSize = streamManager.getRemoteMaxHeaderListSize(); + if (maxSize != Integer.MAX_VALUE && headerListSize > maxSize) { + throw new IOException("Header list size (" + headerListSize + + ") exceeds limit (" + maxSize + ")"); + } + + ByteBuffer buffer = headerEncodeBuffer.toByteBuffer(); + byte[] result = new byte[buffer.remaining()]; + buffer.get(result); + return result; + } + + private String getAuthority(HttpRequest request) { + String host = request.uri().getHost(); + int port = request.uri().getPort(); + String scheme = request.uri().getScheme(); + if (port == -1 || (port == 443 && "https".equalsIgnoreCase(scheme)) + || (port == 80 && "http".equalsIgnoreCase(scheme))) { + return host; + } + return host + ":" + port; + } + + private String getPath(HttpRequest request) { + String path = request.uri().getRawPath(); + if (path == null || path.isEmpty()) { + path = "/"; + } + String query = request.uri().getRawQuery(); + if (query != null && !query.isEmpty()) { + path = path + "?" + query; + } + return path; + } + + private void failRemainingRequests() { + IOException shutdownError = new IOException("Encoder shutting down"); + WorkItem item; + while ((item = workQueue.poll()) != null) { + if (item instanceof WorkItem.EncodeHeaders h) { + h.streamIdFuture().completeExceptionally(shutdownError); + } + completeItem(item, shutdownError); + } + } + + @Override + public void close() { + accepting = false; + + long deadline = System.currentTimeMillis() + 1000; + while (!workQueue.isEmpty() && System.currentTimeMillis() < deadline) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + + running = false; + var _ignore = workQueue.offer(new WorkItem.Shutdown()); + + if (workerThread != null) { + workerThread.interrupt(); + try { + workerThread.join(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + failRemainingRequests(); + } + + void shutdownNow() { + accepting = false; + running = false; + var _ignore = workQueue.offer(new WorkItem.Shutdown()); + if (workerThread != null) { + workerThread.interrupt(); + } + + failRemainingRequests(); + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamEvent.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamEvent.java new file mode 100644 index 000000000..40f407ebc --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamEvent.java @@ -0,0 +1,73 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import java.io.IOException; +import java.util.List; +import software.amazon.smithy.java.http.client.h2.hpack.HpackDecoder; + +/** + * Events that can occur on an HTTP/2 stream. + * + *

This sealed interface represents all possible events that the connection's + * reader thread can deliver to a stream. Using a single event queue with typed + * events simplifies the exchange implementation: + *

    + *
  • Single source of truth - one queue instead of multiple queues + side channels
  • + *
  • Errors as events - no volatile flags, errors flow through the same queue
  • + *
  • Explicit ordering - events arrive in wire order
  • + *
  • HPACK safety - headers are pre-decoded by reader thread before becoming events
  • + *
+ */ +sealed interface StreamEvent { + /** + * Pre-decoded response headers from the reader thread. + * + *

HPACK decoding happens in the reader thread to ensure dynamic table + * updates are processed in frame order across all streams on the connection. + * + * @param fields the decoded header fields + * @param endStream true if FLAG_END_STREAM was set on the HEADERS frame + */ + record Headers(List fields, boolean endStream) implements StreamEvent {} + + /** + * Per-stream error (RST_STREAM received, protocol error, etc.). + */ + record StreamError(H2Exception cause) implements StreamEvent {} + + /** + * Connection-level error (GOAWAY, I/O error, etc.). + */ + record ConnectionError(IOException cause) implements StreamEvent {} + + /** + * Data chunk from response DATA frame. + * + *

Implements {@link StreamEvent} to participate in the unified event queue. + * This avoids wrapper allocations on the hot data path. + * + *

Invariants: + *

    + *
  • Non-end chunks always have {@code endStream == false}
  • + *
  • Only the {@link #END} sentinel or a trailing chunk has {@code endStream == true}
  • + *
  • {@code data} is null only for the {@link #END} sentinel
  • + *
+ */ + record DataChunk(byte[] data, int offset, int length, boolean endStream) implements StreamEvent { + private static final byte[] EMPTY_BYTES = new byte[0]; + + /** Sentinel for end of stream. */ + static final DataChunk END = new DataChunk(null, 0, 0, true); + + /** Singleton empty chunk for empty DATA frames without END_STREAM. */ + static final DataChunk EMPTY = new DataChunk(EMPTY_BYTES, 0, 0, false); + + boolean isEnd() { + return this == END || endStream; + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTable.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTable.java new file mode 100644 index 000000000..fc04f11dc --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTable.java @@ -0,0 +1,208 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2.hpack; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayDeque; +import java.util.Deque; + +/** + * HPACK dynamic table implementation from RFC 7541 Section 2.3.2. + * + *

The dynamic table is a FIFO queue of header field entries. New entries + * are added at the front (lowest index), and entries are evicted from the + * back (highest index) when the table size exceeds the maximum. + * + *

Dynamic table indices start at 62 (after the 61 static table entries). + * Index 62 is the most recently added entry. + * + *

This implementation uses linear scans for lookups. The typical dynamic table + * size is small (< 128 entries with default 4KB limit) so linear scans have good + * cache locality and avoid the overhead of maintaining index maps that shift on + * every add operation. + * + *

Header names must be lowercase as required by HTTP/2 (RFC 7540 Section 8.1.2). + * Name matching uses case-sensitive String.equals(). + */ +final class DynamicTable { + + /** + * Each entry has 32 bytes of overhead. + */ + private static final int ENTRY_OVERHEAD = 32; + + private final Deque entries = new ArrayDeque<>(); + private int currentSize = 0; + private int maxSize; + + /** + * Header field entry with cached size to avoid recomputation during eviction. + */ + record HeaderField(String name, String value, int size) {} + + /** + * Create a dynamic table with the given maximum size. + * + * @param maxSize maximum table size in bytes + */ + DynamicTable(int maxSize) { + this.maxSize = maxSize; + } + + /** + * Get the current number of entries in the table. + * + * @return entry count + */ + int length() { + return entries.size(); + } + + /** + * Get the current size of the table in bytes. + * + * @return current size + */ + int size() { + return currentSize; + } + + /** + * Get the maximum size of the table in bytes. + * + * @return maximum size + */ + int maxSize() { + return maxSize; + } + + /** + * Set a new maximum table size. + * + *

If the new size is smaller than current size, entries are evicted. + * + * @param newMaxSize new maximum size in bytes + */ + void setMaxSize(int newMaxSize) { + this.maxSize = newMaxSize; + evictToSize(newMaxSize); + } + + /** + * Add a new entry to the dynamic table. + * + *

The entry is added at index 62 (first dynamic index). + * Existing entries shift to higher indices. + * + * @param name header name + * @param value header value + */ + void add(String name, String value) { + int entrySize = entrySize(name, value); + + // If entry does not fit even in empty table, don't add it, but still evict to make room as per spec + if (entrySize > maxSize) { + clear(); + return; + } + + // Evict entries until there's room + evictToSize(maxSize - entrySize); + + // Add new entry at front with cached size + entries.addFirst(new HeaderField(name, value, entrySize)); + currentSize += entrySize; + } + + /** + * Get header field at the given index. + * + * @param index dynamic table index (62 + offset) + * @return header field + * @throws IndexOutOfBoundsException if index is out of range + */ + HeaderField get(int index) { + int offset = index - StaticTable.SIZE - 1; // Convert to 0-based offset + if (offset < 0 || offset >= entries.size()) { + throw new IndexOutOfBoundsException("Dynamic table index out of range: " + + index + " (table has " + entries.size() + " entries)"); + } + + // Linear scan to find entry at offset + int i = 0; + for (HeaderField field : entries) { + if (i++ == offset) { + return field; + } + } + + // Should never reach here given bounds check above + throw new AssertionError("Unreachable: offset in range but entry not found"); + } + + /** + * Find the index of a full match (name + value) in the dynamic table. + * + * @param name header name + * @param value header value + * @return dynamic table index (62+) if found, -1 otherwise + */ + int findFullMatch(String name, String value) { + int index = StaticTable.SIZE + 1; + for (HeaderField field : entries) { + if (field.name().equals(name) && field.value().equals(value)) { + return index; + } + index++; + } + return -1; + } + + /** + * Find the index of a name-only match in the dynamic table. + * + * @param name header name + * @return dynamic table index (62+) if found, -1 otherwise + */ + int findNameMatch(String name) { + int index = StaticTable.SIZE + 1; + for (HeaderField field : entries) { + if (field.name().equals(name)) { + return index; + } + index++; + } + return -1; + } + + /** + * Clear all entries from the table. + */ + void clear() { + entries.clear(); + currentSize = 0; + } + + /** + * Calculate the size of an entry per RFC 7541 Section 4.1. + * Size = length(name) in octets + length(value) in octets + 32 + * + *

Uses ISO-8859-1 encoding as per HPACK string literal spec (RFC 7541 Section 5.2). + */ + static int entrySize(String name, String value) { + // RFC 7541 uses ISO-8859-1 encoding - must count bytes, not characters + return name.getBytes(StandardCharsets.ISO_8859_1).length + + value.getBytes(StandardCharsets.ISO_8859_1).length + + ENTRY_OVERHEAD; + } + + private void evictToSize(int targetSize) { + while (currentSize > targetSize && !entries.isEmpty()) { + HeaderField evicted = entries.removeLast(); + currentSize -= evicted.size(); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java new file mode 100644 index 000000000..8c229e07a --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java @@ -0,0 +1,275 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2.hpack; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * HPACK decoder for HTTP/2 header decompression (RFC 7541). + * + *

This decoder decompresses HTTP headers from HPACK format back to name-value pairs. + * + *

Thread safety: This class is NOT thread-safe. Each HTTP/2 connection should have its own decoder instance. + */ +public final class HpackDecoder { + + private final DynamicTable dynamicTable; + private final int maxHeaderListSize; + + /** Current position during decoding, reset at start of each decodeBlock call. */ + private int decodePos; + + /** + * Decoded header field. + * + * @param name Name of the header. + * @param value Value of the header. + */ + public record HeaderField(String name, String value) {} + + /** + * Create a decoder with the given maximum dynamic table size. + * + * @param maxTableSize maximum dynamic table size in bytes + */ + public HpackDecoder(int maxTableSize) { + this(maxTableSize, 8192); + } + + /** + * Create a decoder with the given limits. + * + * @param maxTableSize maximum dynamic table size in bytes + * @param maxHeaderListSize maximum size of decoded header list + */ + public HpackDecoder(int maxTableSize, int maxHeaderListSize) { + this.dynamicTable = new DynamicTable(maxTableSize); + this.maxHeaderListSize = maxHeaderListSize; + } + + /** + * Set the maximum dynamic table size. + * + * @param maxSize new maximum size in bytes + */ + public void setMaxTableSize(int maxSize) { + dynamicTable.setMaxSize(maxSize); + } + + /** + * Decode a header block. + * + * @param data the HPACK-encoded header block + * @return list of decoded header fields + * @throws IOException if decoding fails + */ + public List decode(byte[] data) throws IOException { + return decode(data, 0, data.length); + } + + /** + * Decode a header block. + * + * @param data buffer containing HPACK-encoded header block + * @param offset start offset in buffer + * @param length number of bytes to decode + * @return list of decoded header fields + * @throws IOException if decoding fails + */ + public List decode(byte[] data, int offset, int length) throws IOException { + return decodeBlock(data, offset, length); + } + + /** + * Get a header field from the indexed tables. + */ + private HeaderField getIndexedField(int index) throws IOException { + if (index <= 0) { + throw new IOException("Invalid HPACK index: " + index); + } + + if (index <= StaticTable.SIZE) { + return new HeaderField(StaticTable.getName(index), StaticTable.getValue(index)); + } else { + DynamicTable.HeaderField field = dynamicTable.get(index); + return new HeaderField(field.name(), field.value()); + } + } + + /** + * Get a header name from the indexed tables. + */ + private String getIndexedName(int index) throws IOException { + if (index <= 0) { + throw new IOException("Invalid HPACK name index: " + index); + } + + if (index <= StaticTable.SIZE) { + return StaticTable.getName(index); + } else { + return dynamicTable.get(index).name(); + } + } + + /** + * Decode an integer with the given prefix size. + * Updates decodePos and returns the decoded value. + */ + private int decodeInteger(byte[] data, int prefixBits) throws IOException { + if (decodePos >= data.length) { + throw new IOException("Incomplete HPACK integer: no data at position " + decodePos); + } + int maxPrefix = (1 << prefixBits) - 1; + int value = data[decodePos] & maxPrefix; + decodePos++; + + if (value < maxPrefix) { + return value; + } + + int shift = 0; + int b; + do { + if (decodePos >= data.length) { + throw new IOException("Incomplete HPACK integer"); + } + b = data[decodePos] & 0xFF; + decodePos++; + value += (b & 0x7F) << shift; + shift += 7; + + if (shift > 28) { + throw new IOException("HPACK integer overflow"); + } + } while ((b & 0x80) != 0); + + return value; + } + + /** + * Decode a string. + * Updates decodePos and returns the decoded string. + */ + private String decodeString(byte[] data) throws IOException { + if (decodePos >= data.length) { + throw new IOException("Incomplete HPACK string: no data at position " + decodePos); + } + boolean huffman = (data[decodePos] & 0x80) != 0; + + int length = decodeInteger(data, 7); + + if (decodePos + length > data.length) { + throw new IOException("HPACK string length exceeds buffer"); + } + + String str; + if (huffman) { + str = Huffman.decode(data, decodePos, length); + } else { + str = new String(data, decodePos, length, java.nio.charset.StandardCharsets.ISO_8859_1); + } + decodePos += length; + + return str; + } + + /** + * Decode a literal header field. + * Updates decodePos and returns the decoded header field. + */ + private HeaderField decodeLiteralField(byte[] data, int prefixBits) throws IOException { + int nameIndex = decodeInteger(data, prefixBits); + + String name; + if (nameIndex > 0) { + name = getIndexedName(nameIndex); + } else { + name = decodeString(data); + } + + return new HeaderField(name, decodeString(data)); + } + + /** + * Decode a header block. + * + * @param data buffer containing HPACK-encoded header block + * @param offset start offset in buffer + * @param length number of bytes to decode + * @return list of decoded header fields + * @throws IOException if decoding fails + */ + public List decodeBlock(byte[] data, int offset, int length) throws IOException { + List headers = new ArrayList<>(); + decodePos = offset; + int end = offset + length; + int totalSize = 0; + boolean headerFieldSeen = false; + + while (decodePos < end) { + int b = data[decodePos] & 0xFF; + + HeaderField field; + if ((b & 0x80) != 0) { + // Indexed representation: 1xxxxxxx + int index = decodeInteger(data, 7); + field = getIndexedField(index); + headerFieldSeen = true; + } else if ((b & 0x40) != 0) { + // Literal with indexing: 01xxxxxx + field = decodeLiteralField(data, 6); + dynamicTable.add(field.name(), field.value()); + headerFieldSeen = true; + } else if ((b & 0x20) != 0) { + // Dynamic table size update: 001xxxxx + // RFC 7541 Section 4.2: MUST occur at the beginning of header block + if (headerFieldSeen) { + throw new IOException("Dynamic table size update MUST occur at beginning of header block"); + } + int newSize = decodeInteger(data, 5); + dynamicTable.setMaxSize(newSize); + continue; + } else { + // Literal never indexed (0001xxxx) or without indexing (0000xxxx) + field = decodeLiteralField(data, 4); + headerFieldSeen = true; + } + + // RFC 9113 Section 8.2: Field names MUST NOT contain uppercase characters + validateHeaderName(field.name()); + + // Check header list size per RFC 7541 Section 4.1. + // Since HPACK-decoded strings are ISO-8859-1, each char is one byte, + // so we can use length() directly instead of allocating byte arrays. + totalSize += field.name().length() + field.value().length() + 32; + if (totalSize > maxHeaderListSize) { + throw new IOException("Header list exceeds maximum size: " + totalSize + " > " + maxHeaderListSize); + } + + headers.add(field); + } + + return headers; + } + + /** + * Validate header field name per RFC 9113 Section 8.2. + * + * @param name header field name + * @throws IOException if name contains invalid characters + */ + private void validateHeaderName(String name) throws IOException { + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + // RFC 9113 Section 8.2: Field names MUST NOT contain uppercase characters + if (c >= 'A' && c <= 'Z') { + throw new IOException("Header field name contains uppercase character: '" + name); + } + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoder.java new file mode 100644 index 000000000..ee0d1d045 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoder.java @@ -0,0 +1,237 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2.hpack; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Set; + +/** + * HPACK encoder for HTTP/2 header compression (RFC 7541). + * + *

Thread safety: This class is NOT thread-safe. Each HTTP/2 connection should have its own encoder instance. + */ +public final class HpackEncoder { + + // Headers that should never be indexed (sensitive data) + private static final Set NEVER_INDEX_HEADERS = Set.of( + "authorization", + "cookie", + "proxy-authorization", + "set-cookie"); + + private final DynamicTable dynamicTable; + private final boolean useHuffman; + + // Track pending table size update to emit at start of next header block (RFC 7541 Section 4.2) + // -1 means no update pending + private int pendingTableSizeUpdate = -1; + + /** + * Create an encoder with the given maximum dynamic table size. + * + * @param maxTableSize maximum dynamic table size in bytes + */ + public HpackEncoder(int maxTableSize) { + this(maxTableSize, true); + } + + /** + * Create an encoder with the given maximum dynamic table size. + * + * @param maxTableSize maximum dynamic table size in bytes + * @param useHuffman whether to use Huffman encoding for strings + */ + public HpackEncoder(int maxTableSize, boolean useHuffman) { + this.dynamicTable = new DynamicTable(maxTableSize); + this.useHuffman = useHuffman; + } + + /** + * Set the maximum dynamic table size. + * + *

This should be called when receiving a SETTINGS frame with + * SETTINGS_HEADER_TABLE_SIZE. Per RFC 7541 Section 4.2, the encoder + * MUST signal the change to the decoder at the start of the next header block + * (only if the size actually changed). + * + * @param maxSize new maximum size in bytes + */ + public void setMaxTableSize(int maxSize) { + int currentMaxSize = dynamicTable.maxSize(); + // Only emit table size update if the size actually changed + if (maxSize != currentMaxSize) { + // Apply immediately to dynamic table (evicts entries if needed) + dynamicTable.setMaxSize(maxSize); + // Mark that we need to emit a table size update in the next header block + pendingTableSizeUpdate = maxSize; + } + } + + /** + * Emit any pending dynamic table size update. + * + *

Per RFC 7541 Section 4.2, when SETTINGS_HEADER_TABLE_SIZE is received, + * the encoder MUST signal the change at the start of the next header block + * by emitting a dynamic table size update instruction. + * + *

This method MUST be called once at the start of each header block + * (before encoding any headers). + * + * @param out output stream to write the update to + * @throws IOException if writing fails + */ + public void beginHeaderBlock(OutputStream out) throws IOException { + if (pendingTableSizeUpdate >= 0) { + // Emit dynamic table size update (RFC 7541 Section 6.3) + // Format: 001xxxxx (5-bit prefix for max size) + encodeInteger(out, pendingTableSizeUpdate, 5, 0x20); + pendingTableSizeUpdate = -1; + } + } + + /** + * Encode a single header field. + * + * @param out output stream to write encoded bytes + * @param name header name (lowercase) + * @param value header value + * @param sensitive whether this header contains sensitive data + * @throws IOException if encoding fails + */ + public void encodeHeader(OutputStream out, String name, String value, boolean sensitive) + throws IOException { + + // Sensitive headers should never be indexed + if (sensitive || NEVER_INDEX_HEADERS.contains(name)) { + encodeLiteralNeverIndexed(out, name, value); + return; + } + + // Try to find full match in static table + int staticIndex = StaticTable.findFullMatch(name, value); + if (staticIndex > 0) { + encodeIndexed(out, staticIndex); + return; + } + + // Try to find full match in dynamic table + int dynamicIndex = dynamicTable.findFullMatch(name, value); + if (dynamicIndex > 0) { + encodeIndexed(out, dynamicIndex); + return; + } + + // Try to find name match for literal with indexing + int nameIndex = StaticTable.findNameMatch(name); + if (nameIndex < 0) { + nameIndex = dynamicTable.findNameMatch(name); + } + + // Encode as literal with indexing (adds to dynamic table) + encodeLiteralWithIndexing(out, nameIndex, name, value); + + // Add to dynamic table + dynamicTable.add(name, value); + } + + /** + * Encode a header using indexed representation. + * Format: 1xxxxxxx (7-bit prefix) + */ + private void encodeIndexed(OutputStream out, int index) throws IOException { + encodeInteger(out, index, 7, 0x80); + } + + /** + * Encode a header as literal with indexing. + * Format: 01xxxxxx (6-bit prefix for index) + */ + private void encodeLiteralWithIndexing( + OutputStream out, + int nameIndex, + String name, + String value + ) throws IOException { + if (nameIndex > 0) { + // Indexed name + encodeInteger(out, nameIndex, 6, 0x40); + } else { + // New name + out.write(0x40); + encodeString(out, name); + } + encodeString(out, value); + } + + /** + * Encode a header as literal never indexed. + * Format: 0001xxxx (4-bit prefix for index) + */ + private void encodeLiteralNeverIndexed(OutputStream out, int nameIndex, String name, String value) + throws IOException { + if (nameIndex > 0) { + encodeInteger(out, nameIndex, 4, 0x10); + } else { + out.write(0x10); + encodeString(out, name); + } + encodeString(out, value); + } + + private void encodeLiteralNeverIndexed(OutputStream out, String name, String value) throws IOException { + int nameIndex = StaticTable.findNameMatch(name); + if (nameIndex < 0) { + nameIndex = dynamicTable.findNameMatch(name); + } + encodeLiteralNeverIndexed(out, Math.max(nameIndex, 0), name, value); + } + + /** + * Encode an integer with the given prefix size. + * RFC 7541 Section 5.1 + */ + private void encodeInteger(OutputStream out, int value, int prefixBits, int prefix) throws IOException { + int maxPrefix = (1 << prefixBits) - 1; + + if (value < maxPrefix) { + out.write(prefix | value); + } else { + out.write(prefix | maxPrefix); + value -= maxPrefix; + while (value >= 128) { + out.write((value & 0x7F) | 0x80); + value >>= 7; + } + out.write(value); + } + } + + /** + * Encode a string, using Huffman encoding if it saves space. + * RFC 7541 Section 5.2 + */ + private void encodeString(OutputStream out, String str) throws IOException { + // Convert to bytes once and reuse for both length calculation and encoding + byte[] raw = str.getBytes(StandardCharsets.ISO_8859_1); + + if (useHuffman) { + int huffmanLen = Huffman.encodedLength(raw); + if (huffmanLen < raw.length) { + // Use Huffman encoding + byte[] huffman = Huffman.encode(raw); + encodeInteger(out, huffman.length, 7, 0x80); // H=1 + out.write(huffman); + return; + } + } + + // Use raw encoding + encodeInteger(out, raw.length, 7, 0x00); // H=0 + out.write(raw); + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/Huffman.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/Huffman.java new file mode 100644 index 000000000..3444f8114 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/Huffman.java @@ -0,0 +1,769 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2.hpack; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +/** + * HPACK Huffman encoding/decoding from RFC 7541 Appendix B. + * + *

This implementation uses table-driven encoding and a finite state machine + * for decoding, optimized for the HTTP/2 header compression use case. + */ +final class Huffman { + + private Huffman() {} + + /** + * Huffman codes for each byte value (0-255), from RFC 7541 Appendix B. + */ + private static final int[] CODES = { + 0x1ff8, + 0x7fffd8, + 0xfffffe2, + 0xfffffe3, + 0xfffffe4, + 0xfffffe5, + 0xfffffe6, + 0xfffffe7, + 0xfffffe8, + 0xffffea, + 0x3ffffffc, + 0xfffffe9, + 0xfffffea, + 0x3ffffffd, + 0xfffffeb, + 0xfffffec, + 0xfffffed, + 0xfffffee, + 0xfffffef, + 0xffffff0, + 0xffffff1, + 0xffffff2, + 0x3ffffffe, + 0xffffff3, + 0xffffff4, + 0xffffff5, + 0xffffff6, + 0xffffff7, + 0xffffff8, + 0xffffff9, + 0xffffffa, + 0xffffffb, + 0x14, + 0x3f8, + 0x3f9, + 0xffa, + 0x1ff9, + 0x15, + 0xf8, + 0x7fa, + 0x3fa, + 0x3fb, + 0xf9, + 0x7fb, + 0xfa, + 0x16, + 0x17, + 0x18, + 0x0, + 0x1, + 0x2, + 0x19, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x5c, + 0xfb, + 0x7ffc, + 0x20, + 0xffb, + 0x3fc, + 0x1ffa, + 0x21, + 0x5d, + 0x5e, + 0x5f, + 0x60, + 0x61, + 0x62, + 0x63, + 0x64, + 0x65, + 0x66, + 0x67, + 0x68, + 0x69, + 0x6a, + 0x6b, + 0x6c, + 0x6d, + 0x6e, + 0x6f, + 0x70, + 0x71, + 0x72, + 0xfc, + 0x73, + 0xfd, + 0x1ffb, + 0x7fff0, + 0x1ffc, + 0x3ffc, + 0x22, + 0x7ffd, + 0x3, + 0x23, + 0x4, + 0x24, + 0x5, + 0x25, + 0x26, + 0x27, + 0x6, + 0x74, + 0x75, + 0x28, + 0x29, + 0x2a, + 0x7, + 0x2b, + 0x76, + 0x2c, + 0x8, + 0x9, + 0x2d, + 0x77, + 0x78, + 0x79, + 0x7a, + 0x7b, + 0x7ffe, + 0x7fc, + 0x3ffd, + 0x1ffd, + 0xffffffc, + 0xfffe6, + 0x3fffd2, + 0xfffe7, + 0xfffe8, + 0x3fffd3, + 0x3fffd4, + 0x3fffd5, + 0x3fffd6, + 0x3fffd7, + 0x3fffd8, + 0x3fffd9, + 0x3fffda, + 0x3fffdb, + 0x3fffdc, + 0x3fffdd, + 0x3fffde, + 0x3fffdf, + 0x3fffe0, + 0x3fffe1, + 0x3fffe2, + 0x3fffe3, + 0x3fffe4, + 0x3fffe5, + 0x3fffe6, + 0x3fffe7, + 0x3fffe8, + 0x3fffe9, + 0x3fffea, + 0x3fffeb, + 0xffffec, + 0x3fffec, + 0x3fffed, + 0x3fffee, + 0x3fffef, + 0x3ffff0, + 0x3ffff1, + 0x3ffff2, + 0x3ffff3, + 0x3ffff4, + 0x3ffff5, + 0x3ffff6, + 0x3ffff7, + 0x3ffff8, + 0x3ffff9, + 0x3ffffa, + 0x3ffffb, + 0xfffffb, + 0xfffffc, + 0xfffffd, + 0xfffffe, + 0xffffff, + 0x1ffffec, + 0x1ffffed, + 0x1ffffee, + 0x1ffffef, + 0x1fffff0, + 0x1fffff1, + 0x1fffff2, + 0x1fffff3, + 0x1fffff4, + 0x1fffff5, + 0x1fffff6, + 0x1fffff7, + 0x1fffff8, + 0x1fffff9, + 0x1fffffa, + 0x1fffffb, + 0x1fffffc, + 0x1fffffd, + 0x1fffffe, + 0x1ffffff, + 0x3fffffc, + 0x3fffffd, + 0x3fffffe, + 0x3ffffff, + 0x7fffffc, + 0x7fffffd, + 0x7fffffe, + 0x7ffffff, + 0xffffffc, + 0xffffffd, + 0xffffffe, + 0xfffffff, + 0x10000000, + 0x10000001, + 0x10000002, + 0x10000003, + 0x10000004, + 0x10000005, + 0x10000006, + 0x10000007, + 0x10000008, + 0x10000009, + 0x1000000a, + 0x1000000b, + 0x1000000c, + 0x1000000d, + 0x1000000e, + 0x1000000f, + 0x10000010, + 0x10000011, + 0x10000012, + 0x10000013, + 0x10000014, + 0x10000015, + 0x10000016, + 0x10000017, + 0x10000018, + 0x10000019, + 0x1000001a, + 0x1000001b, + 0x1000001c, + 0x1000001d, + 0x1000001e, + 0x1000001f, + 0x10000020, + 0x10000021, + 0x10000022, + 0x10000023, + 0x10000024, + 0x10000025, + 0x10000026, + 0x10000027, + 0x10000028, + 0x10000029, + 0x1000002a, + 0x1000002b, + 0x1000002c + }; + + /** + * Huffman code lengths (in bits) for each byte value (0-255), from RFC 7541 Appendix B. + */ + private static final byte[] LENGTHS = { + 13, + 23, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 24, + 30, + 28, + 28, + 30, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 30, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 6, + 10, + 10, + 12, + 13, + 6, + 8, + 11, + 10, + 10, + 8, + 11, + 8, + 6, + 6, + 6, + 5, + 5, + 5, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 7, + 8, + 15, + 6, + 12, + 10, + 13, + 6, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 8, + 7, + 8, + 13, + 19, + 13, + 14, + 6, + 15, + 5, + 6, + 5, + 6, + 5, + 6, + 6, + 6, + 5, + 7, + 7, + 6, + 6, + 6, + 5, + 6, + 7, + 6, + 5, + 5, + 6, + 7, + 7, + 7, + 7, + 7, + 15, + 11, + 14, + 13, + 28, + 20, + 22, + 20, + 20, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 24, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 24, + 24, + 24, + 24, + 24, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 26, + 26, + 26, + 26, + 27, + 27, + 27, + 27, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28 + }; + + // EOS (end-of-string) symbol code + private static final int EOS_CODE = 0x3fffffff; + private static final int EOS_LENGTH = 30; + + // Decode flags + private static final int FLAG_EMIT = 0x01; // Emit a decoded byte + private static final int FLAG_ACCEPTED = 0x02; // Valid end state + private static final int FLAG_FAIL = 0x04; // Invalid sequence + + /** + * Huffman decoding table using finite state machine. + * Each entry is {next_state, flags, emitted_byte}. + * The table is indexed by (state << 4) | nibble. + * + *

This table was generated from the Huffman tree in RFC 7541. + * States 0-511 represent positions in the decoding tree (511 nodes max). + */ + private static final int[][] DECODE_TABLE = buildDecodeTable(); + + /** + * Encode bytes using Huffman coding. + * + * @param data the bytes to encode + * @return Huffman-encoded bytes + */ + static byte[] encode(byte[] data) { + int capacity = encodedLength(data); + byte[] buf = new byte[capacity]; + int pos = 0; + long current = 0; + int bits = 0; + + for (byte b : data) { + int index = b & 0xFF; + int code = CODES[index]; + int length = LENGTHS[index]; + + current <<= length; + current |= code; + bits += length; + + while (bits >= 8) { + bits -= 8; + buf[pos++] = (byte) (current >> bits); + } + } + + // Pad with EOS prefix to byte boundary + if (bits > 0) { + current <<= (8 - bits); + current |= (EOS_CODE >> (EOS_LENGTH - (8 - bits))); + buf[pos++] = (byte) current; + } + + return (pos == capacity) ? buf : Arrays.copyOf(buf, pos); + } + + /** + * Calculate the encoded length of a byte array without actually encoding it. + * + * @param data the bytes to measure + * @return length in bytes when Huffman-encoded + */ + static int encodedLength(byte[] data) { + int bits = 0; + for (byte b : data) { + bits += LENGTHS[b & 0xFF]; + } + return (bits + 7) / 8; + } + + /** + * Decode Huffman-encoded bytes to a string. + * + * @param data the buffer containing Huffman-encoded bytes + * @param offset start offset in buffer + * @param length number of bytes to decode + * @return decoded string + * @throws IOException if decoding fails (invalid Huffman sequence) + */ + static String decode(byte[] data, int offset, int length) throws IOException { + // HPACK Huffman expands by at most ~1.6x for typical headers + byte[] buf = new byte[length * 2]; + int pos = 0; + int state = 0; + boolean accepted = true; + + for (int i = offset; i < offset + length; i++) { + int b = data[i] & 0xFF; + + // Process high nibble + int index = (state << 4) | (b >> 4); + state = DECODE_TABLE[index][0]; + int flags = DECODE_TABLE[index][1]; + + if ((flags & FLAG_FAIL) != 0) { + throw new IOException("Invalid Huffman encoding"); + } + if ((flags & FLAG_EMIT) != 0) { + if (pos >= buf.length) { + buf = Arrays.copyOf(buf, buf.length * 2); + } + buf[pos++] = (byte) DECODE_TABLE[index][2]; + } + + // Process low nibble + index = (state << 4) | (b & 0x0F); + state = DECODE_TABLE[index][0]; + flags = DECODE_TABLE[index][1]; + + if ((flags & FLAG_FAIL) != 0) { + throw new IOException("Invalid Huffman encoding"); + } + if ((flags & FLAG_EMIT) != 0) { + if (pos >= buf.length) { + buf = Arrays.copyOf(buf, buf.length * 2); + } + buf[pos++] = (byte) DECODE_TABLE[index][2]; + } + // Final validity depends only on last nibble's state + accepted = (flags & FLAG_ACCEPTED) != 0; + } + + if (!accepted) { + throw new IOException("Invalid Huffman encoding: incomplete sequence"); + } + + return new String(buf, 0, pos, StandardCharsets.ISO_8859_1); + } + + private static int[][] buildDecodeTable() { + // State machine with up to 512 states (Huffman tree has 511 nodes: 256 leaves + 255 internal) + int[][] table = new int[512 * 16][3]; + + // Initialize all entries to fail state + for (int[] row : table) { + row[1] = FLAG_FAIL; + } + + // Tree as parallel arrays: left[i] and right[i] are children of state i (-1 = none) + // symbol[i] is decoded symbol at state i (-1 = non-terminal) + int[] left = new int[512]; + int[] right = new int[512]; + int[] symbol = new int[512]; + Arrays.fill(left, -1); + Arrays.fill(right, -1); + Arrays.fill(symbol, -1); + int numStates = 1; // state 0 is root + + // Build tree + for (int sym = 0; sym < 256; sym++) { + int code = CODES[sym]; + int len = LENGTHS[sym]; + int state = 0; + for (int i = len - 1; i >= 0; i--) { + int bit = (code >> i) & 1; + int[] children = (bit == 0) ? left : right; + if (children[state] == -1) { + children[state] = numStates++; + } + state = children[state]; + } + symbol[state] = sym; + } + + // Build state transition table for each nibble (4 bits at a time) + for (int startState = 0; startState < numStates; startState++) { + for (int nibble = 0; nibble < 16; nibble++) { + int cur = startState; + int emitted = -1; + boolean failed = false; + + // Process 4 bits + for (int i = 3; i >= 0; i--) { + int bit = (nibble >> i) & 1; + cur = (bit == 0) ? left[cur] : right[cur]; + if (cur == -1) { + failed = true; + break; + } + if (symbol[cur] != -1) { + emitted = symbol[cur]; + cur = 0; // reset to root + } + } + + if (failed) { + continue; // already initialized to FAIL + } + + int idx = (startState << 4) | nibble; + table[idx][0] = cur; + table[idx][1] = (emitted >= 0 ? FLAG_EMIT : 0) + | (canBeEosPadded(cur, symbol, right) ? FLAG_ACCEPTED : 0); + table[idx][2] = Math.max(emitted, 0); + } + } + + return table; + } + + /** + * Check if a state can be valid EOS padding per RFC 7541. + * A state is accepted if following all 1-bits (right children) for up to 7 bits + * never reaches a terminal symbol. + */ + private static boolean canBeEosPadded(int state, int[] symbol, int[] right) { + // root is always accepted + if (state == 0) { + return true; + } + + for (int i = 0; i < 7; i++) { + if (state == -1) { + return false; + } else if (symbol[state] != -1) { + return false; // would decode a symbol - invalid padding + } + state = right[state]; // follow 1-bit (EOS is all 1s) + } + + return true; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTable.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTable.java new file mode 100644 index 000000000..b8b77891d --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTable.java @@ -0,0 +1,211 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2.hpack; + +/** + * HPACK static table from RFC 7541 Appendix A. + * + *

The static table consists of 61 predefined header field entries, where index 0 is unused. + * + *

This implementation uses length-based bucketing for fast lookups with zero per-lookup + * allocations. Entries are grouped by header name length, so lookups only scan candidates + * with matching name length (typically 1-3 entries per bucket). + */ +final class StaticTable { + + private StaticTable() {} + + /** + * Number of entries in the static table. + */ + static final int SIZE = 61; + + /** + * Static table entries. Index 0 is unused (indices are 1-based). + * Each entry is {name, value} where value may be empty string. + */ + private static final String[][] ENTRIES = { + null, // Index 0 unused + {":authority", ""}, // 1 + {":method", "GET"}, // 2 + {":method", "POST"}, // 3 + {":path", "/"}, // 4 + {":path", "/index.html"}, // 5 + {":scheme", "http"}, // 6 + {":scheme", "https"}, // 7 + {":status", "200"}, // 8 + {":status", "204"}, // 9 + {":status", "206"}, // 10 + {":status", "304"}, // 11 + {":status", "400"}, // 12 + {":status", "404"}, // 13 + {":status", "500"}, // 14 + {"accept-charset", ""}, // 15 + {"accept-encoding", "gzip, deflate"}, // 16 + {"accept-language", ""}, // 17 + {"accept-ranges", ""}, // 18 + {"accept", ""}, // 19 + {"access-control-allow-origin", ""}, // 20 + {"age", ""}, // 21 + {"allow", ""}, // 22 + {"authorization", ""}, // 23 + {"cache-control", ""}, // 24 + {"content-disposition", ""}, // 25 + {"content-encoding", ""}, // 26 + {"content-language", ""}, // 27 + {"content-length", ""}, // 28 + {"content-location", ""}, // 29 + {"content-range", ""}, // 30 + {"content-type", ""}, // 31 + {"cookie", ""}, // 32 + {"date", ""}, // 33 + {"etag", ""}, // 34 + {"expect", ""}, // 35 + {"expires", ""}, // 36 + {"from", ""}, // 37 + {"host", ""}, // 38 + {"if-match", ""}, // 39 + {"if-modified-since", ""}, // 40 + {"if-none-match", ""}, // 41 + {"if-range", ""}, // 42 + {"if-unmodified-since", ""}, // 43 + {"last-modified", ""}, // 44 + {"link", ""}, // 45 + {"location", ""}, // 46 + {"max-forwards", ""}, // 47 + {"proxy-authenticate", ""}, // 48 + {"proxy-authorization", ""}, // 49 + {"range", ""}, // 50 + {"referer", ""}, // 51 + {"refresh", ""}, // 52 + {"retry-after", ""}, // 53 + {"server", ""}, // 54 + {"set-cookie", ""}, // 55 + {"strict-transport-security", ""}, // 56 + {"transfer-encoding", ""}, // 57 + {"user-agent", ""}, // 58 + {"vary", ""}, // 59 + {"via", ""}, // 60 + {"www-authenticate", ""} // 61 + }; + + /** + * Maximum header name length in the static table. + */ + private static final int MAX_NAME_LEN; + + /** + * Empty bucket for lengths with no entries (avoids null checks in lookups). + */ + private static final int[] EMPTY_BUCKET = new int[0]; + + /** + * Buckets of static table indices grouped by header name length. + * NAME_BUCKETS_BY_LEN[len] contains indices of entries whose name has that length. + * Empty buckets use EMPTY_BUCKET to avoid null checks. + */ + private static final int[][] NAME_BUCKETS_BY_LEN; + + static { + // Find max name length + int maxLen = 0; + for (int i = 1; i <= SIZE; i++) { + int len = ENTRIES[i][0].length(); + if (len > maxLen) { + maxLen = len; + } + } + MAX_NAME_LEN = maxLen; + + // First pass: count entries per length + int[] counts = new int[MAX_NAME_LEN + 1]; + for (int i = 1; i <= SIZE; i++) { + counts[ENTRIES[i][0].length()]++; + } + + // Allocate buckets (empty bucket for lengths with no entries) + int[][] buckets = new int[MAX_NAME_LEN + 1][]; + for (int len = 0; len <= MAX_NAME_LEN; len++) { + buckets[len] = counts[len] > 0 ? new int[counts[len]] : EMPTY_BUCKET; + } + + // Second pass: fill buckets + int[] pos = new int[MAX_NAME_LEN + 1]; + for (int i = 1; i <= SIZE; i++) { + int len = ENTRIES[i][0].length(); + buckets[len][pos[len]++] = i; + } + + NAME_BUCKETS_BY_LEN = buckets; + } + + /** + * Get the header name at the given index. + * + * @param index 1-based index into static table + * @return header name + * @throws IndexOutOfBoundsException if index is out of range + */ + static String getName(int index) { + if (index < 1 || index > SIZE) { + throw new IndexOutOfBoundsException("Static table index out of range: " + index); + } + return ENTRIES[index][0]; + } + + /** + * Get the header value at the given index. + * + * @param index 1-based index into static table + * @return header value (may be empty string) + * @throws IndexOutOfBoundsException if index is out of range + */ + static String getValue(int index) { + if (index < 1 || index > SIZE) { + throw new IndexOutOfBoundsException("Static table index out of range: " + index); + } + return ENTRIES[index][1]; + } + + /** + * Find index for a full match (name + value). + * + * @param name header name + * @param value header value + * @return index if found, -1 otherwise + */ + static int findFullMatch(String name, String value) { + int len = name.length(); + if (len > MAX_NAME_LEN) { + return -1; + } + for (int idx : NAME_BUCKETS_BY_LEN[len]) { + String[] e = ENTRIES[idx]; + if (e[0].equals(name) && e[1].equals(value)) { + return idx; + } + } + return -1; + } + + /** + * Find index for a name-only match. + * + * @param name header name + * @return index of first entry with this name, -1 if not found + */ + static int findNameMatch(String name) { + int len = name.length(); + if (len <= MAX_NAME_LEN) { + for (int idx : NAME_BUCKETS_BY_LEN[len]) { + if (ENTRIES[idx][0].equals(name)) { + return idx; + } + } + } + return -1; + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/Http2IntegrationTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/Http2IntegrationTest.java new file mode 100644 index 000000000..025ac7a87 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/Http2IntegrationTest.java @@ -0,0 +1,296 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; +import io.netty.handler.codec.http2.Http2DataFrame; +import io.netty.handler.codec.http2.Http2FrameCodecBuilder; +import io.netty.handler.codec.http2.Http2FrameStream; +import io.netty.handler.codec.http2.Http2Headers; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import io.netty.handler.codec.http2.Http2ResetFrame; +import io.netty.handler.codec.http2.Http2SecurityUtil; +import io.netty.handler.ssl.ApplicationProtocolConfig; +import io.netty.handler.ssl.ApplicationProtocolNames; +import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SupportedCipherSuiteFilter; +import io.netty.handler.ssl.util.SelfSignedCertificate; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.dns.DnsResolver; + +/** + * Integration test for HTTP/2 client implementation. + */ +public class Http2IntegrationTest { + + private static final byte[] CONTENT = "{\"status\":\"ok\"}".getBytes(StandardCharsets.UTF_8); + + private EventLoopGroup bossGroup; + private EventLoopGroup workerGroup; + private Channel serverChannel; + private String baseUrl; + private HttpClient client; + + @BeforeEach + void setup() throws Exception { + bossGroup = new NioEventLoopGroup(1); + workerGroup = new NioEventLoopGroup(4); + + // Start HTTP/2 server + SelfSignedCertificate ssc = new SelfSignedCertificate("localhost"); + SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) + .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE) + .applicationProtocolConfig(new ApplicationProtocolConfig( + ApplicationProtocolConfig.Protocol.ALPN, + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + ApplicationProtocolNames.HTTP_2)) + .build(); + + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + ch.pipeline().addLast(sslCtx.newHandler(ch.alloc())); + ch.pipeline().addLast(new Http2AlpnHandler()); + } + }); + + serverChannel = b.bind(0).sync().channel(); + int port = ((InetSocketAddress) serverChannel.localAddress()).getPort(); + baseUrl = "https://localhost:" + port; + System.out.println("Started HTTP/2 test server on port " + port); + + // Create trust-all SSL context + TrustManager[] trustAllCerts = new TrustManager[] { + new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[0]; + } + + public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {} + + public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {} + } + }; + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + + // Create HTTP/2 client + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + + client = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_2) + .dnsResolver(staticDns) + .sslContext(sslContext) + .build()) + .build(); + } + + @AfterEach + void teardown() throws Exception { + if (client != null) + client.close(); + if (serverChannel != null) + serverChannel.close().sync(); + if (bossGroup != null) + bossGroup.shutdownGracefully().sync(); + if (workerGroup != null) + workerGroup.shutdownGracefully().sync(); + } + + @Test + void testSimpleGet() throws Exception { + URI uri = URI.create(baseUrl + "/test"); + HttpRequest request = HttpRequest.builder() + .uri(uri) + .method("GET") + .build(); + + System.out.println("Sending HTTP/2 GET request to " + uri); + HttpResponse response = client.send(request); + + System.out.println("Response status: " + response.statusCode()); + byte[] bodyBytes = response.body().asByteBuffer().array(); + String bodyStr = new String(bodyBytes, StandardCharsets.UTF_8); + System.out.println("Response body: " + bodyStr); + + assertEquals(200, response.statusCode()); + assertEquals("{\"status\":\"ok\"}", bodyStr); + } + + @Test + void testSequentialMultiplexedRequests() throws Exception { + // Test sequential requests on same connection to verify basic multiplexing + URI uri = URI.create(baseUrl + "/test"); + + for (int i = 0; i < 3; i++) { + HttpRequest request = HttpRequest.builder() + .uri(uri) + .method("GET") + .build(); + + System.out.println("Sending sequential request " + i); + HttpResponse response = client.send(request); + assertEquals(200, response.statusCode(), "Request " + i + " should succeed"); + System.out.println("Sequential request " + i + " succeeded"); + } + } + + @Test + void testConcurrentMultiplexedRequests() throws Exception { + URI uri = URI.create(baseUrl + "/test"); + int concurrency = 3; // Start with small number to debug + + var errors = new java.util.concurrent.atomic.AtomicInteger(0); + var success = new java.util.concurrent.atomic.AtomicInteger(0); + var latch = new java.util.concurrent.CountDownLatch(concurrency); + + try (var executor = java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < concurrency; i++) { + final int reqNum = i; + executor.submit(() -> { + try { + HttpRequest request = HttpRequest.builder() + .uri(uri) + .method("GET") + .build(); + + HttpResponse response = client.send(request); + + if (response.statusCode() == 200) { + success.incrementAndGet(); + } else { + System.err.println("Request " + reqNum + " got status: " + response.statusCode()); + errors.incrementAndGet(); + } + } catch (Exception e) { + System.err.println("Request " + reqNum + " failed: " + e.getMessage()); + e.printStackTrace(); + errors.incrementAndGet(); + } finally { + latch.countDown(); + } + }); + } + + latch.await(30, java.util.concurrent.TimeUnit.SECONDS); + } + + System.out.println("Success: " + success.get() + ", Errors: " + errors.get()); + assertEquals(concurrency, success.get(), "All requests should succeed"); + assertEquals(0, errors.get(), "No errors should occur"); + } + + /** + * ALPN handler for HTTP/2. + */ + private static class Http2AlpnHandler extends ApplicationProtocolNegotiationHandler { + Http2AlpnHandler() { + super(ApplicationProtocolNames.HTTP_2); + } + + @Override + protected void configurePipeline(ChannelHandlerContext ctx, String protocol) { + System.out.println("Server negotiated protocol: " + protocol); + if (ApplicationProtocolNames.HTTP_2.equals(protocol)) { + ctx.pipeline() + .addLast( + Http2FrameCodecBuilder.forServer().build(), + new Http2Handler()); + } else { + throw new IllegalStateException("Unknown protocol: " + protocol); + } + } + } + + /** + * HTTP/2 request handler. + */ + private static class Http2Handler extends ChannelInboundHandlerAdapter { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + if (msg instanceof Http2HeadersFrame headersFrame) { + System.out.println("Server received HEADERS on stream " + headersFrame.stream().id() + + ", endStream=" + headersFrame.isEndStream()); + if (headersFrame.isEndStream()) { + sendResponse(ctx, headersFrame.stream()); + } + } else if (msg instanceof Http2DataFrame dataFrame) { + System.out.println("Server received DATA on stream " + dataFrame.stream().id() + + ", endStream=" + dataFrame.isEndStream()); + dataFrame.release(); + if (dataFrame.isEndStream()) { + sendResponse(ctx, dataFrame.stream()); + } + } else if (msg instanceof Http2ResetFrame resetFrame) { + System.out.println("Server received RST_STREAM on stream " + resetFrame.stream().id() + + ", errorCode=" + resetFrame.errorCode()); + } else { + System.out.println("Server received unknown frame: " + msg.getClass().getSimpleName()); + } + } + + private void sendResponse(ChannelHandlerContext ctx, Http2FrameStream stream) { + System.out.println("Server sending response on stream " + stream.id()); + Http2Headers headers = new DefaultHttp2Headers() + .status("200") + .set("content-type", "application/json") + .setInt("content-length", CONTENT.length); + ctx.write(new DefaultHttp2HeadersFrame(headers).stream(stream)); + ctx.writeAndFlush(new DefaultHttp2DataFrame( + Unpooled.wrappedBuffer(CONTENT), + true).stream(stream)); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + System.err.println("Server error: " + cause.getMessage()); + cause.printStackTrace(); + ctx.close(); + } + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpBinTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpBinTest.java new file mode 100644 index 000000000..6f4d81854 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpBinTest.java @@ -0,0 +1,353 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.zip.GZIPInputStream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.io.datastream.DataStream; + +/** + * Integration tests using httpbin.org to verify HTTP client functionality. + * + * This test class validates the HTTP client against real-world scenarios using httpbin.org, + * a free HTTP testing service. + */ +class HttpBinTest { + + private static final String HTTPBIN_URL = "https://httpbin.org"; + + private HttpClient client; + + @BeforeEach + void setUp() { + client = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder().build()) + .build(); + } + + @AfterEach + void tearDown() throws IOException { + if (client != null) { + client.close(); + } + } + + // ======================================================================== + // Example 1: Creating a Client + // ======================================================================== + + @Test + void testClientCreation_withDefaults() throws IOException { + HttpClient simpleClient = HttpClient.builder() + .build(); + assertNotNull(simpleClient); + simpleClient.close(); + } + + // ======================================================================== + // Example 2: Sending Request and Getting Normal Response (Non-Streaming) + // ======================================================================== + + @Test + void testSimpleGetRequest() throws IOException { + // Simple GET request + HttpRequest getRequest = HttpRequest.builder() + .uri(URI.create(HTTPBIN_URL + "/get")) + .method("GET") + .build(); + + HttpResponse response = client.send(getRequest); + int status = response.statusCode(); + assertEquals(200, status, "Expected 200 OK status"); + + // Read entire body into memory + try (InputStream in = response.body().asInputStream()) { + String body = new String(in.readAllBytes(), StandardCharsets.UTF_8); + assertNotNull(body); + assertTrue(body.contains("\"url\""), "Response should contain URL field"); + } + } + + @Test + void testPostRequestWithJsonBody() throws IOException { + // POST request with JSON body + String jsonPayload = "{\"name\":\"John\",\"email\":\"john@example.com\"}"; + byte[] jsonBytes = jsonPayload.getBytes(StandardCharsets.UTF_8); + + HttpRequest postRequest = HttpRequest.builder() + .uri(URI.create(HTTPBIN_URL + "/post")) + .method("POST") + .body(DataStream.ofInputStream(new ByteArrayInputStream(jsonBytes))) + .withAddedHeader("Content-Type", "application/json") + .withAddedHeader("Content-Length", String.valueOf(jsonBytes.length)) + .build(); + + HttpResponse response = client.send(postRequest); + assertEquals(200, response.statusCode(), "Expected 200 OK status"); + + try (InputStream in = response.body().asInputStream()) { + String responseBody = new String(in.readAllBytes(), StandardCharsets.UTF_8); + assertTrue(responseBody.contains("John"), "Response should contain posted data"); + assertTrue(responseBody.contains("john@example.com"), "Response should contain posted email"); + } + } + + @Test + void testRequestWithCustomHeaders() throws IOException { + // Request with custom headers + HttpRequest authedRequest = HttpRequest.builder() + .uri(URI.create(HTTPBIN_URL + "/headers")) + .method("GET") + .withAddedHeader("Authorization", "Bearer my-access-token") + .withAddedHeader("X-API-Version", "2.0") + .build(); + + HttpResponse response = client.send(authedRequest); + assertEquals(200, response.statusCode()); + + try (InputStream in = response.body().asInputStream()) { + String body = new String(in.readAllBytes(), StandardCharsets.UTF_8); + assertTrue(body.contains("Bearer my-access-token"), "Should echo Authorization header"); + assertTrue(body.contains("2.0"), "Should echo X-API-Version header"); + } + } + + // ======================================================================== + // Example 3: Sending Streaming Request, Normal Response + // ======================================================================== + + @Test + void testStreamingRequestWithChunkedEncoding() throws IOException { + // Upload with streaming request using chunked encoding + HttpRequest uploadRequest = HttpRequest.builder() + .uri(URI.create(HTTPBIN_URL + "/post")) + .method("POST") + .withAddedHeader("Content-Type", "application/octet-stream") + .withAddedHeader("Transfer-Encoding", "chunked") + .build(); + + HttpExchange exchange = client.newExchange(uploadRequest); + + // Stream upload data in chunks + try (OutputStream out = exchange.requestBody()) { + // Simulate streaming data + byte[] chunk1 = "Hello, ".getBytes(StandardCharsets.UTF_8); + byte[] chunk2 = "World!".getBytes(StandardCharsets.UTF_8); + + out.write(chunk1); + out.flush(); + out.write(chunk2); + out.flush(); + } + + // Get buffered response + int status = exchange.responseStatusCode(); + assertEquals(200, status, "Upload should succeed"); + + try (InputStream responseBody = exchange.responseBody()) { + String result = new String(responseBody.readAllBytes(), StandardCharsets.UTF_8); + assertTrue(result.contains("Hello, World!"), "Response should contain uploaded data"); + } + + exchange.close(); + } + + // ======================================================================== + // Example 4: Streaming Request and Response (Sequential - Not Bidirectional) + // ======================================================================== + + @Test + void testStreamingRequestThenStreamingResponse() throws IOException { + // Send data, then receive streaming response + String data = "{\"test\":\"streaming\"}"; + byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8); + + HttpRequest streamRequest = HttpRequest.builder() + .uri(URI.create(HTTPBIN_URL + "/post")) + .method("POST") + .withAddedHeader("Content-Type", "application/json") + .withAddedHeader("Content-Length", String.valueOf(dataBytes.length)) + .build(); + + var exchange = client.newExchange(streamRequest); + + // First: Stream the request + try (OutputStream out = exchange.requestBody()) { + out.write(dataBytes); + } + + // Then: Stream the response + try (InputStream in = exchange.responseBody(); + BufferedReader reader = new BufferedReader(new InputStreamReader(in))) { + + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + + assertTrue(response.toString().contains("streaming"), + "Response should contain posted data"); + } + + exchange.close(); + } + + // ======================================================================== + // Example 5: Bidirectional Request/Response (True Streaming) + // NOTE: This test is commented out because it requires preview features + // (StructuredTaskScope) which need --enable-preview flag + // ======================================================================== + + // @Test + // void testBidirectionalStreamingPattern() throws Exception { + // // This demonstrates the API pattern for bidirectional streaming + // // In practice, you'd use this with a server that supports SSE, WebSocket, etc. + // // Requires StructuredTaskScope which is a preview feature in Java 21 + // } + + // ======================================================================== + // Additional Tests: Error Handling and Edge Cases + // ======================================================================== + + @Test + void test404NotFound() throws IOException { + HttpRequest notFoundRequest = HttpRequest.builder() + .uri(URI.create(HTTPBIN_URL + "/status/404")) + .method("GET") + .build(); + + HttpResponse response = client.send(notFoundRequest); + assertEquals(404, response.statusCode(), "Should return 404"); + } + + @Test + void testDelayedResponse() throws IOException { + // httpbin.org provides /delay/N endpoint for testing timeouts + HttpRequest delayRequest = HttpRequest.builder() + .uri(URI.create(HTTPBIN_URL + "/delay/1")) + .method("GET") + .build(); + + HttpResponse response = client.send(delayRequest); + assertEquals(200, response.statusCode(), "Should handle delayed response"); + } + + @Test + void testResponseWithSpecificStatus() throws IOException { + // Test specific status codes + HttpRequest statusRequest = HttpRequest.builder() + .uri(URI.create(HTTPBIN_URL + "/status/201")) + .method("GET") + .build(); + + HttpResponse response = client.send(statusRequest); + assertEquals(201, response.statusCode(), "Should return requested status code"); + } + + @Test + void testGzipCompression() throws IOException { + // httpbin.org can return gzip compressed responses + HttpRequest gzipRequest = HttpRequest.builder() + .uri(URI.create(HTTPBIN_URL + "/gzip")) + .method("GET") + .build(); + + HttpResponse response = client.send(gzipRequest); + assertEquals(200, response.statusCode()); + + // Manually handle gzip decompression based on Content-Encoding header + InputStream bodyStream = response.body().asInputStream(); + String contentEncoding = response.headers().firstValue("Content-Encoding"); + + if (contentEncoding != null && contentEncoding.toLowerCase().contains("gzip")) { + bodyStream = new GZIPInputStream(bodyStream); + } + + try (InputStream in = bodyStream) { + String body = new String(in.readAllBytes(), StandardCharsets.UTF_8); + assertTrue(body.contains("gzipped"), "Should receive gzipped response"); + } + } + + // ======================================================================== + // Example 6: Expect: 100-continue + // ======================================================================== + + @Test + void testExpect100Continue() throws IOException { + // Large payload to justify using 100-continue + String largePayload = "x".repeat(10000); + byte[] payloadBytes = largePayload.getBytes(StandardCharsets.UTF_8); + + HttpRequest expectContinueRequest = HttpRequest.builder() + .uri(URI.create(HTTPBIN_URL + "/post")) + .method("POST") + .body(DataStream.ofInputStream(new ByteArrayInputStream(payloadBytes))) + .withAddedHeader("Content-Type", "text/plain") + .withAddedHeader("Content-Length", String.valueOf(payloadBytes.length)) + .withAddedHeader("Expect", "100-continue") + .build(); + + HttpResponse response = client.send(expectContinueRequest); + assertEquals(200, response.statusCode(), "Should handle 100-continue and return 200"); + + try (InputStream in = response.body().asInputStream()) { + String responseBody = new String(in.readAllBytes(), StandardCharsets.UTF_8); + // httpbin echoes the data back in the response + assertTrue(responseBody.contains("\"data\": \"" + largePayload.substring(0, 20)), + "Response should contain posted data"); + } + } + + @Test + void testExpect100ContinueWithExchange() throws IOException { + // Test streaming with 100-continue using exchange API + HttpRequest expectContinueRequest = HttpRequest.builder() + .uri(URI.create(HTTPBIN_URL + "/post")) + .method("POST") + .withAddedHeader("Content-Type", "application/octet-stream") + .withAddedHeader("Transfer-Encoding", "chunked") + .withAddedHeader("Expect", "100-continue") + .build(); + + HttpExchange exchange = client.newExchange(expectContinueRequest); + + // Write request body - client waits for 100 Continue before sending + try (OutputStream out = exchange.requestBody()) { + byte[] data = "Test data for 100-continue".getBytes(StandardCharsets.UTF_8); + out.write(data); + } + + // Read response + assertEquals(200, exchange.responseStatusCode(), "Should receive 200 after 100-continue"); + + try (InputStream in = exchange.responseBody()) { + String response = new String(in.readAllBytes(), StandardCharsets.UTF_8); + assertTrue(response.contains("Test data for 100-continue"), + "Response should contain posted data"); + } + + exchange.close(); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 35e2acaba..fc1714a8f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,6 +27,7 @@ include(":auth-api") include(":framework-errors") include(":http:http-api") include(":http:http-binding") +include(":http:http-client") include(":retries-api") include(":tracing-api") From ed973b2e734fc7092b1f867d39080bbf4db472f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Sugawara=20=28=E2=88=A9=EF=BD=80-=C2=B4=29?= =?UTF-8?q?=E2=8A=83=E2=94=81=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E?= Date: Thu, 4 Dec 2025 17:25:59 -0800 Subject: [PATCH 02/60] Add a Netty server for local testing --- .../it/RequestResponseHttp11ClearTest.java | 79 +++ .../it/RequestResponseHttp11WithTlsTest.java | 92 ++++ .../it/RequestResponseHttp2ClearTest.java | 83 +++ .../smithy/java/http/client/it/TestUtils.java | 159 ++++++ .../client/it/server/Http11ClientHandler.java | 25 + .../it/server/Http11ClientHandlerFactory.java | 14 + .../http/client/it/server/Http11Handler.java | 53 ++ .../client/it/server/Http2ClientHandler.java | 20 + .../it/server/Http2ClientHandlerFactory.java | 14 + .../server/Http2ConnectionFrameHandler.java | 44 ++ .../it/server/Http2StreamFrameHandler.java | 59 +++ .../MultiplexingHttp11ClientHandler.java | 57 ++ .../MultiplexingHttp2ClientHandler.java | 41 ++ .../client/it/server/NettyTestLogger.java | 497 ++++++++++++++++++ .../client/it/server/NettyTestServer.java | 182 +++++++ .../RequestCapturingHttp11ClientHandler.java | 80 +++ .../RequestCapturingHttp2ClientHandler.java | 71 +++ .../client/it/server/ServerInitializer.java | 118 +++++ .../it/server/TestCertificateGenerator.java | 137 +++++ .../TextResponseHttp11ClientHandler.java | 66 +++ .../TextResponseHttp2ClientHandler.java | 48 ++ 21 files changed, 1939 insertions(+) create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/TestUtils.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandler.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandlerFactory.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11Handler.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandler.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandlerFactory.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2StreamFrameHandler.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp11ClientHandler.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp2ClientHandler.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/NettyTestLogger.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/NettyTestServer.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp11ClientHandler.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp2ClientHandler.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/ServerInitializer.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TestCertificateGenerator.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp11ClientHandler.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java new file mode 100644 index 000000000..f6d60dbf8 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java @@ -0,0 +1,79 @@ +package software.amazon.smithy.java.http.client.it; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.TextResponseHttp11ClientHandler; +import software.amazon.smithy.java.http.api.HttpVersion; + +public class RequestResponseHttp11ClearTest { + private static final String RESPONSE_CONTENTS = "Response sent from Http11ClearTest"; + private static final String REQUEST_CONTENTS = "Request sent from Http11ClearTest"; + private RequestCapturingHttp11ClientHandler requestCapturingHandler; + private NettyTestServer server; + private HttpClient client; + + @BeforeEach + void setUp() throws Exception { + requestCapturingHandler = new RequestCapturingHttp11ClientHandler(); + var multiplexer = new MultiplexingHttp11ClientHandler(requestCapturingHandler, + new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS)); + server = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory((ctx) -> multiplexer) + .build(); + server.start(); + + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + client = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .dnsResolver(staticDns) + .build()) + .build(); + } + + @AfterEach + void tearDown() throws Exception { + server.stop(); + client.close(); + } + + @Test + void canSendRequestAndReadResponse() throws Exception { + // -- Arrange + var request = TestUtils.plainTextHttp11Request("http://localhost:" + server.getPort(), REQUEST_CONTENTS); + + // -- Act + var response = client.send(request); + var bodyByteBuf = response.body().asByteBuffer(); + var bytes = new byte[bodyByteBuf.remaining()]; + bodyByteBuf.get(bytes); + var responseBody = new String(bytes, StandardCharsets.UTF_8); + + // -- Assert + var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); + assertEquals(REQUEST_CONTENTS, capturedRequestBody); + assertEquals(RESPONSE_CONTENTS, responseBody); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java new file mode 100644 index 000000000..584cb980e --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java @@ -0,0 +1,92 @@ +package software.amazon.smithy.java.http.client.it; + +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; +import software.amazon.smithy.java.http.client.it.server.TextResponseHttp11ClientHandler; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.smithy.java.http.client.it.TestUtils.createClientSslContext; +import static software.amazon.smithy.java.http.client.it.TestUtils.createServerSslContextBuilder; + +public class RequestResponseHttp11WithTlsTest { + private static final String RESPONSE_CONTENTS = "Response sent from Http2WithTls"; + private static final String REQUEST_CONTENTS = "Request sent from Http2WithTls"; + private static TestCertificateGenerator.CertificateBundle bundle; + private NettyTestServer server; + private RequestCapturingHttp11ClientHandler requestCapturingHandler; + private HttpClient client; + + @BeforeAll + static void beforeAll() throws Exception { + bundle = TestCertificateGenerator.generateCertificates(); + } + + @BeforeEach + void setUp() throws Exception { + requestCapturingHandler = new RequestCapturingHttp11ClientHandler(); + var multiplexer = new MultiplexingHttp11ClientHandler(requestCapturingHandler, + new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS)); + server = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory((ctx) -> multiplexer) + .sslContextBuilder(createServerSslContextBuilder(bundle)) + .build(); + server.start(); + + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + client = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .sslContext(createClientSslContext(bundle)) + .dnsResolver(staticDns) + .build()) + .build(); + + } + + @AfterEach + void tearDown() { + server.stop(); + } + + @Test + public void canSendRequestAndReadResponse() throws Exception { + // -- Arrange + var request = TestUtils.plainTextHttp2Request("https://localhost:" + server.getPort(), REQUEST_CONTENTS); + + // -- Act + var response = client.send(request); + var bodyByteBuf = response.body().asByteBuffer(); + var bytes = new byte[bodyByteBuf.remaining()]; + bodyByteBuf.get(bytes); + var responseBody = new String(bytes, StandardCharsets.UTF_8); + client.close(); + + // -- Assert + var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); + assertEquals(REQUEST_CONTENTS, capturedRequestBody); + assertEquals(RESPONSE_CONTENTS, responseBody); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java new file mode 100644 index 000000000..f5c94305b --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java @@ -0,0 +1,83 @@ +package software.amazon.smithy.java.http.client.it; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.TextResponseHttp2ClientHandler; +import software.amazon.smithy.java.http.api.HttpVersion; + +public class RequestResponseHttp2ClearTest { + private static final String RESPONSE_CONTENTS = "Response sent from Http2ClearTest"; + private static final String REQUEST_CONTENTS = "Request sent from Http2ClearTest"; + private RequestCapturingHttp2ClientHandler requestCapturingHandler; + private NettyTestServer server; + private HttpClient client; + + @BeforeEach + void setUp() throws Exception { + requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); + var multiplexer = new MultiplexingHttp2ClientHandler(requestCapturingHandler, + new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS)); + server = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory((ctx) -> multiplexer) + // fixed port to wireshark the traffic + .port(8080) + .build(); + server.start(); + + + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + client = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) + .dnsResolver(staticDns) + .build()) + .build(); + } + + @AfterEach + void tearDown() { + server.stop(); + } + + @Test + void canSendRequestAndReadResponse() throws Exception { + // -- Arrange + var request = TestUtils.plainTextHttp2Request("http://localhost:" + server.getPort(), REQUEST_CONTENTS); + + // -- Act + var response = client.send(request); + var bodyByteBuf = response.body().asByteBuffer(); + var bytes = new byte[bodyByteBuf.remaining()]; + bodyByteBuf.get(bytes); + var responseBody = new String(bytes, StandardCharsets.UTF_8); + client.close(); + + // -- Assert + var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); + assertEquals(REQUEST_CONTENTS, capturedRequestBody); + assertEquals(RESPONSE_CONTENTS, responseBody); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/TestUtils.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/TestUtils.java new file mode 100644 index 000000000..254c8ee0d --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/TestUtils.java @@ -0,0 +1,159 @@ +package software.amazon.smithy.java.http.client.it; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Flow; + +import io.netty.handler.ssl.SslContextBuilder; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; +import software.amazon.smithy.java.io.datastream.DataStream; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +public class TestUtils { + static final List IPSUM_LOREM = getIpsumLorem(); + + private TestUtils() { + } + + public static HttpRequest plainTextHttp11Request(String uri, String contents) { + return plainTextRequest(HttpVersion.HTTP_1_1, uri, contents); + } + + public static HttpRequest plainTextHttp2Request(String uri, String contents) { + return plainTextRequest(HttpVersion.HTTP_2, uri, contents); + } + + public static HttpRequest plainTextRequest(HttpVersion version, String uri, String contents) { + return request(version, uri, DataStream.ofString(contents)); + } + + public static DataStream streamingBody(Iterable values) { + return DataStream.ofPublisher(new StreamingPublisher(values), "text/plain", -1); + } + + public static HttpRequest request(HttpVersion version, String uri, DataStream body) { + try { + var headers = HttpHeaders.ofModifiable(); + headers.addHeader("content-type", "text/plain"); + if (body.contentLength() >= 0) { + headers.addHeader("content-length", Long.toString(body.contentLength())); + } + return HttpRequest.builder() + .httpVersion(version) + .uri(new URI(uri)) + .headers(headers) + .method("POST") + .body(body) + .build(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + public static SslContextBuilder createServerSslContextBuilder( + TestCertificateGenerator.CertificateBundle bundle + ) throws Exception { + return SslContextBuilder + .forServer(bundle.serverPrivateKey, bundle.serverCertificate); + } + + public static SSLContext createClientSslContext( + TestCertificateGenerator.CertificateBundle bundle + ) throws Exception { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, createTrustManager(bundle.caCertificate), new SecureRandom()); + return sslContext; + } + + public static TrustManager[] createTrustManager(X509Certificate caCert) throws Exception { + // Create a KeyStore and add the CA certificate + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); // Initialize empty keystore + keyStore.setCertificateEntry("ca-cert", caCert); + + // Initialize TrustManagerFactory with the KeyStore + TrustManagerFactory tmf = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm() + ); + tmf.init(keyStore); + + // Return the first TrustManager (typically there's only one) + return tmf.getTrustManagers(); + } + + private static List getIpsumLorem() { + return Arrays.asList( + "Lorem ipsum dolor sit amet, ", + "consectetur adipiscing elit, sed do ", + "eiusmod tempor incididunt ut ", + "labore et dolore magna aliqua. ", + "Ut enim ad minim veniam, quis ", + "nostrud exercitation ullamco laboris ", + "nisi ut ", + "aliquip ex ea commodo consequat. ", + "Duis aute irure dolor in ", + "reprehenderit in voluptate velit esse ", + "cillum dolore eu fugiat nulla ", + "pariatur. Excepteur sint occaecat ", + "cupidatat non proident, sunt in ", + "culpa qui officia deserunt mollit ", + "anim id est laborum."); + } + + static class StreamingPublisher implements Flow.Publisher { + private final Iterable values; + + StreamingPublisher(Iterable values) { + this.values = values; + } + + @Override + public void subscribe(Flow.Subscriber subscriber) { + subscriber.onSubscribe(new StreamingSubscription(values, subscriber)); + } + } + + static class StreamingSubscription implements Flow.Subscription { + private final Iterator values; + private final Flow.Subscriber subscriber; + private boolean completed = false; + + StreamingSubscription(Iterable values, Flow.Subscriber subscriber) { + this.values = values.iterator(); + this.subscriber = subscriber; + } + + @Override + public void request(long n) { + if (completed) { + return; + } + for (var idx = 0; idx < n && values.hasNext(); idx++) { + var value = ByteBuffer.wrap(values.next().getBytes(StandardCharsets.UTF_8)); + subscriber.onNext(value); + } + if (!values.hasNext()) { + completed = true; + subscriber.onComplete(); + } + } + + @Override + public void cancel() { + subscriber.onComplete(); + } + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandler.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandler.java new file mode 100644 index 000000000..b8547c045 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandler.java @@ -0,0 +1,25 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.LastHttpContent; + +public interface Http11ClientHandler { + + void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request); + + void onRequest(ChannelHandlerContext ctx, HttpRequest request); + + void onContent(ChannelHandlerContext ctx, HttpContent content); + + void onLastContent(ChannelHandlerContext ctx, LastHttpContent content); + + void onException(ChannelHandlerContext ctx, Throwable cause); +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandlerFactory.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandlerFactory.java new file mode 100644 index 000000000..4e311187f --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandlerFactory.java @@ -0,0 +1,14 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.channel.ChannelHandlerContext; + +@FunctionalInterface +public interface Http11ClientHandlerFactory { + + Http11ClientHandler create(ChannelHandlerContext ctx); +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11Handler.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11Handler.java new file mode 100644 index 000000000..25495a25f --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11Handler.java @@ -0,0 +1,53 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.LastHttpContent; + +public class Http11Handler extends ChannelInboundHandlerAdapter { + private final ServerInitializer.Http11ClientHandlers handlers; + + public Http11Handler(ServerInitializer.Http11ClientHandlers handlers) { + this.handlers = handlers; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + if (msg instanceof HttpRequest httpRequest) { + var handler = handlers.create(ctx); + // Initial request line and headers + if (httpRequest instanceof FullHttpRequest fullHttpRequest) { + handler.onFullRequest(ctx, fullHttpRequest); + } else { + handler.onRequest(ctx, httpRequest); + } + } else if (msg instanceof HttpContent httpContent) { + var handler = handlers.get(ctx); + if (httpContent instanceof LastHttpContent httpLastContent) { + handler.onLastContent(ctx, httpLastContent); + } else { + handler.onContent(ctx, httpContent); + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + var handler = handlers.get(ctx); + handler.onException(ctx, cause); + ctx.close(); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + ctx.fireChannelInactive(); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandler.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandler.java new file mode 100644 index 000000000..6cdade519 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandler.java @@ -0,0 +1,20 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http2.Http2DataFrame; +import io.netty.handler.codec.http2.Http2HeadersFrame; + +public interface Http2ClientHandler { + + void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame); + + void onDataFrame(ChannelHandlerContext ctx, Http2DataFrame frame); + + void onException(ChannelHandlerContext ctx, Throwable cause); + +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandlerFactory.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandlerFactory.java new file mode 100644 index 000000000..e8257869c --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandlerFactory.java @@ -0,0 +1,14 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.channel.ChannelHandlerContext; + +@FunctionalInterface +public interface Http2ClientHandlerFactory { + + Http2ClientHandler create(ChannelHandlerContext ctx); +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java new file mode 100644 index 000000000..103a9827e --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.http2.Http2GoAwayFrame; +import io.netty.handler.codec.http2.Http2PingFrame; +import io.netty.handler.codec.http2.Http2SettingsAckFrame; +import io.netty.handler.codec.http2.Http2SettingsFrame; + +public class Http2ConnectionFrameHandler extends ChannelInboundHandlerAdapter { + private static final NettyTestLogger LOGGER = NettyTestLogger.getLogger(Http2ConnectionFrameHandler.class); + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + if (msg instanceof Http2SettingsFrame settingsFrame) { + // SETTINGS are automatically acknowledged by Http2FrameCodec + LOGGER.info(ctx.channel(), "Received SETTINGS frame: {}", settingsFrame.settings()); + } else if (msg instanceof Http2PingFrame) { + // PING responses are automatically handled by Http2FrameCodec + LOGGER.info(ctx.channel(), "Received PING frame"); + } else if (msg instanceof Http2GoAwayFrame goAwayFrame) { + LOGGER.info(ctx.channel(), + "Received GOAWAY frame, error code: {}, last streamId: {}", + goAwayFrame.errorCode(), + goAwayFrame.lastStreamId()); + } else if (msg instanceof Http2SettingsAckFrame ackFrame) { + LOGGER.info(ctx.channel(), "Received settings ack frame"); + } else { + // Unknown connection-level frame + LOGGER.warn(ctx.channel(), "Received an unknown message: {}", msg); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + LOGGER.warn(ctx.channel(), "Exception caught, closing context", cause); + ctx.close(); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2StreamFrameHandler.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2StreamFrameHandler.java new file mode 100644 index 000000000..7d4e2bc1d --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2StreamFrameHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http2.Http2DataFrame; +import io.netty.handler.codec.http2.Http2Frame; +import io.netty.handler.codec.http2.Http2HeadersFrame; + +class Http2StreamFrameHandler extends SimpleChannelInboundHandler { + + private static final NettyTestLogger LOGGER = NettyTestLogger.getLogger(Http2StreamFrameHandler.class); + private final ServerInitializer.Http2ClientHandlers handlers; + + public Http2StreamFrameHandler(ServerInitializer.Http2ClientHandlers h2ClientHandlers) { + this.handlers = h2ClientHandlers; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, Http2Frame frame) { + if (frame instanceof Http2HeadersFrame headersFrame) { + LOGGER.info(ctx.channel(), "received HTTP/2 headers frame"); + onHeadersRead(ctx, headersFrame); + var handler = handlers.create(ctx); + handler.onHeadersFrame(ctx, headersFrame); + } else if (frame instanceof Http2DataFrame dataFrame) { + LOGGER.info(ctx.channel(), "received HTTP/2 data frame"); + onDataRead(ctx, dataFrame); + var handler = handlers.get(ctx); + handler.onDataFrame(ctx, dataFrame); + } else { + LOGGER.warn(ctx.channel(), "unexpected frame: {}", frame); + } + } + + private void onHeadersRead(ChannelHandlerContext ctx, Http2HeadersFrame headersFrame) { + var headers = headersFrame.headers(); + var method = headers.method().toString(); + var path = headers.path().toString(); + + LOGGER.debug(ctx.channel(), "Received HTTP/2 request for method: {}, path: {}", method, path); + } + + private void onDataRead(ChannelHandlerContext ctx, Http2DataFrame dataFrame) { + LOGGER.debug(ctx.channel(), "Received data with {} bytes", dataFrame.content().readableBytes()); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + var handler = handlers.get(ctx); + handler.onException(ctx, cause); + LOGGER.warn(ctx.channel(), "Exception caught, closing context", cause); + ctx.close(); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp11ClientHandler.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp11ClientHandler.java new file mode 100644 index 000000000..bfcf9c94b --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp11ClientHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.LastHttpContent; +import java.util.Arrays; +import java.util.List; + +public class MultiplexingHttp11ClientHandler implements Http11ClientHandler { + private final List handlers; + + public MultiplexingHttp11ClientHandler(Http11ClientHandler... handlers) { + this.handlers = Arrays.asList(handlers); + } + + @Override + public void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request) { + for (var handler : handlers) { + handler.onFullRequest(ctx, request); + } + } + + @Override + public void onRequest(ChannelHandlerContext ctx, HttpRequest request) { + for (var handler : handlers) { + handler.onRequest(ctx, request); + } + } + + @Override + public void onContent(ChannelHandlerContext ctx, HttpContent content) { + for (var handler : handlers) { + handler.onContent(ctx, content); + } + } + + @Override + public void onLastContent(ChannelHandlerContext ctx, LastHttpContent content) { + for (var handler : handlers) { + handler.onLastContent(ctx, content); + } + } + + @Override + public void onException(ChannelHandlerContext ctx, Throwable cause) { + for (var handler : handlers) { + handler.onException(ctx, cause); + } + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp2ClientHandler.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp2ClientHandler.java new file mode 100644 index 000000000..3e1293e96 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp2ClientHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http2.Http2DataFrame; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import java.util.Arrays; +import java.util.List; + +public class MultiplexingHttp2ClientHandler implements Http2ClientHandler { + private final List handlers; + + public MultiplexingHttp2ClientHandler(Http2ClientHandler... handler) { + this.handlers = Arrays.asList(handler); + } + + @Override + public void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame) { + for (var handler : handlers) { + handler.onHeadersFrame(ctx, frame); + } + } + + @Override + public void onDataFrame(ChannelHandlerContext ctx, Http2DataFrame frame) { + for (var handler : handlers) { + handler.onDataFrame(ctx, frame); + } + } + + @Override + public void onException(ChannelHandlerContext ctx, Throwable cause) { + for (var handler : handlers) { + handler.onException(ctx, cause); + } + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/NettyTestLogger.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/NettyTestLogger.java new file mode 100644 index 000000000..963fac927 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/NettyTestLogger.java @@ -0,0 +1,497 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.channel.Channel; +import software.amazon.smithy.java.logging.InternalLogger; + +public class NettyTestLogger { + + private final InternalLogger logger; + + protected NettyTestLogger(InternalLogger logger) { + this.logger = logger; + } + + /** + * Creates a new Netty logger. + * + * @param clazz The class that creates the logs + * @return The Netty logger + */ + public static NettyTestLogger getLogger(Class clazz) { + return new NettyTestLogger(InternalLogger.getLogger(clazz)); + } + + /** + * Logs a message for the given channel with the TRACE level. + *

+ * ` * @param channel channel + * + * @param message message + */ + public void trace(Channel channel, String message) { + if (logger.isTraceEnabled()) { + logger.trace(addChannelIdToMessage(message, channel)); + } + } + + /** + * Logs a message for the given channel with the INFO level. + * + * @param channel channel + * @param message message format + * @param throwable throwable + */ + public void trace(Channel channel, String message, Throwable throwable) { + if (logger.isTraceEnabled()) { + logger.trace(addChannelIdToMessage(message, channel), throwable); + } + } + + /** + * Logs a message for the given channel with the TRACE level. + * Logs a message for the given channel with the TRACE level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + */ + public void trace(Channel channel, String message, Object p0) { + if (logger.isTraceEnabled()) { + logger.trace(addChannelIdToMessage(message, channel), p0); + } + } + + /** + * Logs a message for the given channel with the TRACE level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + * @param p1 param one + */ + public void trace(Channel channel, String message, Object p0, Object p1) { + if (logger.isTraceEnabled()) { + logger.trace(addChannelIdToMessage(message, channel), p0, p1); + } + } + + /** + * Logs a message for the given channel with the TRACE level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + * @param p1 param one + * @param p2 param two + */ + public void trace(Channel channel, String message, Object p0, Object p1, Object p2) { + if (logger.isTraceEnabled()) { + logger.trace(addChannelIdToMessage(message, channel), p0, p1, p2); + } + } + + /** + * Logs a message for the given channel with the TRACE level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + * @param p1 param one + * @param p2 param two + * @param p3 param three + */ + public void trace(Channel channel, String message, Object p0, Object p1, Object p2, Object p3) { + if (logger.isTraceEnabled()) { + logger.trace(addChannelIdToMessage(message, channel), p0, p1, p2, p3); + } + } + + /** + * Logs a message for the given channel with the TRACE level. + * + * @param channel channel + * @param message message format + * @param args format args + */ + public void trace(Channel channel, String message, Object... args) { + if (logger.isTraceEnabled()) { + logger.trace(addChannelIdToMessage(message, channel), args); + } + } + + /** + * Logs a message for the given channel with the DEBUG level. + * + * @param channel channel + * @param message message + */ + public void debug(Channel channel, String message) { + if (logger.isDebugEnabled()) { + logger.debug(addChannelIdToMessage(message, channel)); + } + } + + /** + * Logs a message for the given channel with the DEBUG level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + */ + public void debug(Channel channel, String message, Object p0) { + if (logger.isDebugEnabled()) { + logger.debug(addChannelIdToMessage(message, channel), p0); + } + } + + /** + * Logs a message for the given channel with the DEBUG level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + * @param p1 param one + */ + public void debug(Channel channel, String message, Object p0, Object p1) { + if (logger.isDebugEnabled()) { + logger.debug(addChannelIdToMessage(message, channel), p0, p1); + } + } + + /** + * Logs a message for the given channel with the DEBUG level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + * @param p1 param one + * @param p2 param two + */ + public void debug(Channel channel, String message, Object p0, Object p1, Object p2) { + if (logger.isDebugEnabled()) { + logger.debug(addChannelIdToMessage(message, channel), p0, p1, p2); + } + } + + /** + * Logs a message for the given channel with the DEBUG level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + * @param p1 param one + * @param p2 param two + * @param p3 param three + */ + public void debug(Channel channel, String message, Object p0, Object p1, Object p2, Object p3) { + if (logger.isDebugEnabled()) { + logger.debug(addChannelIdToMessage(message, channel), p0, p1, p2, p3); + } + } + + /** + * Logs a message for the given channel with the DEBUG level. + * + * @param channel channel + * @param message message format + * @param args format args + */ + public void debug(Channel channel, String message, Object... args) { + if (logger.isDebugEnabled()) { + logger.debug(addChannelIdToMessage(message, channel), args); + } + } + + /** + * Logs a message for the given channel with the INFO level. + * + * @param channel channel + * @param message message + */ + public void info(Channel channel, String message) { + if (logger.isInfoEnabled()) { + logger.info(addChannelIdToMessage(message, channel)); + } + } + + /** + * Logs a message for the given channel with the INFO level. + * + * @param channel channel + * @param message message format + * @param throwable throwable + */ + public void info(Channel channel, String message, Throwable throwable) { + if (logger.isInfoEnabled()) { + logger.info(addChannelIdToMessage(message, channel), throwable); + } + } + + /** + * Logs a message for the given channel with the INFO level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + */ + public void info(Channel channel, String message, Object p0) { + if (logger.isInfoEnabled()) { + logger.info(addChannelIdToMessage(message, channel), p0); + } + } + + /** + * Logs a message for the given channel with the INFO level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + * @param p1 param one + */ + public void info(Channel channel, String message, Object p0, Object p1) { + if (logger.isInfoEnabled()) { + logger.info(addChannelIdToMessage(message, channel), p0, p1); + } + } + + /** + * Logs a message for the given channel with the INFO level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + * @param p1 param one + * @param p2 param two + */ + public void info(Channel channel, String message, Object p0, Object p1, Object p2) { + if (logger.isInfoEnabled()) { + logger.info(addChannelIdToMessage(message, channel), p0, p1, p2); + } + } + + /** + * Logs a message for the given channel with the INFO level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + * @param p1 param one + * @param p2 param two + * @param p3 param three + */ + public void info(Channel channel, String message, Object p0, Object p1, Object p2, Object p3) { + if (logger.isInfoEnabled()) { + logger.info(addChannelIdToMessage(message, channel), p0, p1, p2, p3); + } + } + + /** + * Logs a message for the given channel with the INFO level. + * + * @param channel channel + * @param message message format + * @param args format args + */ + public void info(Channel channel, String message, Object... args) { + if (logger.isInfoEnabled()) { + logger.info(addChannelIdToMessage(message, channel), args); + } + } + + /** + * Logs a message for the given channel with the WARN level. + * + * @param channel channel + * @param message message + */ + public void warn(Channel channel, String message) { + if (logger.isWarnEnabled()) { + logger.warn(addChannelIdToMessage(message, channel)); + } + } + + /** + * Logs a message for the given channel with the WARN level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + */ + public void warn(Channel channel, String message, Object p0) { + if (logger.isWarnEnabled()) { + logger.warn(addChannelIdToMessage(message, channel), p0); + } + } + + /** + * Logs a message for the given channel with the WARN level. + * + * @param channel channel + * @param message message format + * @param throwable throwable + */ + public void warn(Channel channel, String message, Throwable throwable) { + if (logger.isWarnEnabled()) { + logger.warn(addChannelIdToMessage(message, channel), throwable); + } + } + + /** + * Logs a message for the given channel with the WARN level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + * @param p1 param one + */ + public void warn(Channel channel, String message, Object p0, Object p1) { + if (logger.isWarnEnabled()) { + logger.warn(addChannelIdToMessage(message, channel), p0, p1); + } + } + + /** + * Logs a message for the given channel with the WARN level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + * @param p1 param one + * @param p2 param two + */ + public void warn(Channel channel, String message, Object p0, Object p1, Object p2) { + if (logger.isWarnEnabled()) { + logger.warn(addChannelIdToMessage(message, channel), p0, p1, p2); + } + } + + /** + * Logs a message for the given channel with the WARN level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + * @param p1 param one + * @param p2 param two + * @param p3 param three + */ + public void warn(Channel channel, String message, Object p0, Object p1, Object p2, Object p3) { + if (logger.isWarnEnabled()) { + logger.warn(addChannelIdToMessage(message, channel), p0, p1, p2, p3); + } + } + + /** + * Logs a message for the given channel with the WARN level. + * + * @param channel channel + * @param message message format + * @param args format args + */ + public void warn(Channel channel, String message, Object... args) { + if (logger.isWarnEnabled()) { + logger.warn(addChannelIdToMessage(message, channel), args); + } + } + + /** + * Logs a message for the given channel with the ERROR level. + * + * @param channel channel + * @param message message + */ + public void error(Channel channel, String message) { + if (logger.isErrorEnabled()) { + logger.error(addChannelIdToMessage(message, channel)); + } + } + + /** + * Logs a message for the given channel with the ERROR level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + */ + public void error(Channel channel, String message, Object p0) { + if (logger.isErrorEnabled()) { + logger.error(addChannelIdToMessage(message, channel), p0); + } + } + + /** + * Logs a message for the given channel with the ERROR level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + * @param p1 param one + */ + public void error(Channel channel, String message, Object p0, Object p1) { + if (logger.isErrorEnabled()) { + logger.error(addChannelIdToMessage(message, channel), p0, p1); + } + } + + /** + * Logs a message for the given channel with the ERROR level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + * @param p1 param one + * @param p2 param two + */ + public void error(Channel channel, String message, Object p0, Object p1, Object p2) { + if (logger.isErrorEnabled()) { + logger.error(addChannelIdToMessage(message, channel), p0, p1, p2); + } + } + + /** + * Logs a message for the given channel with the ERROR level. + * + * @param channel channel + * @param message message format + * @param p0 param zero + * @param p1 param one + * @param p2 param two + * @param p3 param three + */ + public void error(Channel channel, String message, Object p0, Object p1, Object p2, Object p3) { + if (logger.isErrorEnabled()) { + logger.error(addChannelIdToMessage(message, channel), p0, p1, p2, p3); + } + } + + /** + * Logs a message for the given channel with the ERROR level. + * + * @param channel channel + * @param message message format + * @param args format args + */ + public void error(Channel channel, String message, Object... args) { + if (logger.isErrorEnabled()) { + logger.error(addChannelIdToMessage(message, channel), args); + } + } + + protected String addChannelIdToMessage(String message, Channel channel) { + if (channel == null) { + return message; + } + String id; + if (logger.isDebugEnabled()) { + id = channel.toString(); + } else { + id = channel.id().asShortText(); + } + return String.format("[Channel: %s] %s", id, message); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/NettyTestServer.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/NettyTestServer.java new file mode 100644 index 000000000..514a8a0ba --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/NettyTestServer.java @@ -0,0 +1,182 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.MultiThreadIoEventLoopGroup; +import io.netty.channel.nio.NioIoHandler; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.util.concurrent.DefaultThreadFactory; +import java.net.InetSocketAddress; +import software.amazon.smithy.java.http.api.HttpVersion; + +public class NettyTestServer { + private static final NettyTestLogger LOGGER = NettyTestLogger.getLogger(NettyTestServer.class); + private final EventLoopGroup bossGroup; + private final EventLoopGroup workerGroup; + private final Config config; + private int port; + private Channel channel; + + public NettyTestServer(Builder builder) { + this.port = builder.port; + this.config = builder.buildConfig(); + this.bossGroup = createEventLoopGroup(1); + this.workerGroup = createEventLoopGroup(4); + } + + public static Builder builder() { + return new Builder(); + } + + public void start() throws InterruptedException { + var http2 = config.httpVersion == HttpVersion.HTTP_2; + var bootstrap = new ServerBootstrap(); + bootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(new ServerInitializer(config)); + + channel = bootstrap.bind("127.0.0.1", this.port).sync().channel(); + var address = (InetSocketAddress) channel.localAddress(); + this.port = address.getPort(); + LOGGER.info(null, "Server started on port {}, using: {}", port, (http2 ? " (HTTP/2)" : " (HTTP/1.1)")); + } + + public void stop() { + if (channel != null) { + channel.close().awaitUninterruptibly(); + } + workerGroup.shutdownGracefully(); + bossGroup.shutdownGracefully(); + } + + public int getPort() { + return port; + } + + private EventLoopGroup createEventLoopGroup(Integer nThreads) { + var threadFactory = new DefaultThreadFactory("test-server", true); + if (nThreads != null) { + return new MultiThreadIoEventLoopGroup(nThreads, + threadFactory, + NioIoHandler.newFactory()); + + } + return new MultiThreadIoEventLoopGroup(threadFactory, + NioIoHandler.newFactory()); + } + + /** + * The connection mode for establishing HTTP/2 connections. + */ + public enum H2ConnectionMode { + /** + * Uses ALPN over https and prior knowledge over http. + */ + AUTO, + /** + * Negotiate using Application Layer Protocol Negotiation. This mode requires HTTPS. + */ + ALPN, + /** + * Use prior knowledge. + */ + PRIOR_KNOWLEDGE + } + + public static class Config { + private final int port; + private final HttpVersion httpVersion; + private final H2ConnectionMode h2ConnectionMode; + private final SslContextBuilder sslContextBuilder; + private final Http2ClientHandlerFactory http2HandlerFactory; + private final Http11ClientHandlerFactory http11HandlerFactory; + + public Config(Builder builder) { + this.h2ConnectionMode = builder.h2ConnectionMode; + this.sslContextBuilder = builder.sslContextBuilder; + this.httpVersion = builder.httpVersion; + this.port = builder.port; + this.http2HandlerFactory = builder.http2HandlerFactory; + this.http11HandlerFactory = builder.http11HandlerFactory; + } + + public int port() { + return this.port; + } + + public HttpVersion httpVersion() { + return this.httpVersion; + } + + public H2ConnectionMode h2ConnectionMode() { + return this.h2ConnectionMode; + } + + public SslContextBuilder sslContextBuilder() { + return this.sslContextBuilder; + } + + public Http2ClientHandlerFactory http2HandlerFactory() { + return http2HandlerFactory; + } + + public Http11ClientHandlerFactory http11HandlerFactory() { + return http11HandlerFactory; + } + } + + public static class Builder { + private int port = 0; + private HttpVersion httpVersion = HttpVersion.HTTP_1_1; + private H2ConnectionMode h2ConnectionMode = + H2ConnectionMode.AUTO; + private SslContextBuilder sslContextBuilder; + private Http2ClientHandlerFactory http2HandlerFactory; + private Http11ClientHandlerFactory http11HandlerFactory; + + public Builder port(int port) { + this.port = port; + return this; + } + + public Builder httpVersion(HttpVersion httpVersion) { + this.httpVersion = httpVersion; + return this; + } + + public Builder h2ConnectionMode(H2ConnectionMode h2ConnectionMode) { + this.h2ConnectionMode = h2ConnectionMode; + return this; + } + + public Builder sslContextBuilder(SslContextBuilder sslContextBuilder) { + this.sslContextBuilder = sslContextBuilder; + return this; + } + + public Builder http2HandlerFactory(Http2ClientHandlerFactory http2HandlerFactory) { + this.http2HandlerFactory = http2HandlerFactory; + return this; + } + + public Builder http11HandlerFactory(Http11ClientHandlerFactory http11HandlerFactory) { + this.http11HandlerFactory = http11HandlerFactory; + return this; + } + + public NettyTestServer build() { + return new NettyTestServer(this); + } + + public Config buildConfig() { + return new Config(this); + } + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp11ClientHandler.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp11ClientHandler.java new file mode 100644 index 000000000..7b453b68f --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp11ClientHandler.java @@ -0,0 +1,80 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.LastHttpContent; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RequestCapturingHttp11ClientHandler implements Http11ClientHandler { + private final Map> capturedHeaders = new HashMap<>(); + private final ByteArrayOutputStream capturedBody = new ByteArrayOutputStream(); + private Throwable cause; + + @Override + public void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request) { + captureHeaders(request); + captureBody(request.content()); + } + + @Override + public void onRequest(ChannelHandlerContext ctx, HttpRequest request) { + captureHeaders(request); + } + + @Override + public void onContent(ChannelHandlerContext ctx, HttpContent content) { + captureBody(content.content()); + } + + @Override + public void onLastContent(ChannelHandlerContext ctx, LastHttpContent content) { + captureBody(content.content()); + } + + @Override + public void onException(ChannelHandlerContext ctx, Throwable cause) { + this.cause = cause; + } + + private void captureHeaders(HttpRequest request) { + var headers = request.headers(); + for (var kvp : headers) { + capturedHeaders.computeIfAbsent(kvp.getKey(), k -> new ArrayList<>()).add(kvp.getValue()); + } + } + + private void captureBody(ByteBuf content) { + var bytes = new byte[content.readableBytes()]; + content.readBytes(bytes); + try { + capturedBody.write(bytes); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public Throwable cause() { + return cause; + } + + public Map> capturedHeaders() { + return capturedHeaders; + } + + public ByteArrayOutputStream capturedBody() { + return capturedBody; + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp2ClientHandler.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp2ClientHandler.java new file mode 100644 index 000000000..5d5288011 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp2ClientHandler.java @@ -0,0 +1,71 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http2.Http2DataFrame; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class RequestCapturingHttp2ClientHandler implements Http2ClientHandler { + private final CompletableFuture streamCompleted = new CompletableFuture<>(); + private final Map> capturedHeaders = new HashMap<>(); + private final ByteArrayOutputStream capturedBody = new ByteArrayOutputStream(); + private Throwable cause; + + @Override + public void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame) { + var headers = frame.headers(); + for (var kvp : headers) { + var key = kvp.getKey().toString(); + var value = kvp.getValue().toString(); + capturedHeaders.computeIfAbsent(key, k -> new ArrayList<>()).add(value); + } + } + + @Override + public void onDataFrame(ChannelHandlerContext ctx, Http2DataFrame frame) { + try { + var content = frame.content(); + var bytes = new byte[content.readableBytes()]; + content.readBytes(bytes); + capturedBody.write(bytes); + if (frame.isEndStream()) { + streamCompleted.complete(true); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void onException(ChannelHandlerContext ctx, Throwable cause) { + this.cause = cause; + streamCompleted.completeExceptionally(cause); + } + + public Throwable cause() { + return cause; + } + + public Map> capturedHeaders() { + return capturedHeaders; + } + + public ByteArrayOutputStream capturedBody() { + return capturedBody; + } + + public CompletableFuture streamCompleted() { + return streamCompleted; + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/ServerInitializer.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/ServerInitializer.java new file mode 100644 index 000000000..33d0fe370 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/ServerInitializer.java @@ -0,0 +1,118 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelId; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http2.Http2FrameCodecBuilder; +import io.netty.handler.codec.http2.Http2MultiplexHandler; +import io.netty.handler.ssl.ApplicationProtocolConfig; +import io.netty.handler.ssl.ApplicationProtocolNames; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import javax.net.ssl.SSLException; +import software.amazon.smithy.java.http.api.HttpVersion; + +public class ServerInitializer extends ChannelInitializer { + private final Http2ClientHandlers h2ClientHandlers; + private final Http11ClientHandlers h11ClientHandlers; + private final NettyTestServer.Config config; + + public ServerInitializer(NettyTestServer.Config config) { + this.config = config; + if (config.httpVersion() == HttpVersion.HTTP_2) { + var handlersFactory = Objects.requireNonNull(config.http2HandlerFactory()); + this.h2ClientHandlers = new Http2ClientHandlers(handlersFactory); + this.h11ClientHandlers = null; + } else { + var handlersFactory = Objects.requireNonNull(config.http11HandlerFactory()); + this.h11ClientHandlers = new Http11ClientHandlers(handlersFactory); + this.h2ClientHandlers = null; + } + } + + @Override + protected void initChannel(SocketChannel ch) { + var pipeline = ch.pipeline(); + var sslContextBuilder = config.sslContextBuilder(); + if (sslContextBuilder != null) { + try { + if (config.h2ConnectionMode() == NettyTestServer.H2ConnectionMode.ALPN) { + sslContextBuilder.applicationProtocolConfig(new ApplicationProtocolConfig( + ApplicationProtocolConfig.Protocol.ALPN, + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + ApplicationProtocolNames.HTTP_2)); + } + pipeline.addLast(sslContextBuilder.build().newHandler(ch.alloc())); + } catch (SSLException e) { + throw new RuntimeException(e); + } + } + + if (config.httpVersion() == HttpVersion.HTTP_2) { + // HTTP/2 with prior knowledge + pipeline.addLast(Http2FrameCodecBuilder.forServer().build()); + pipeline.addLast(new Http2MultiplexHandler(new ChannelInitializer<>() { + @Override + protected void initChannel(Channel ch) { + ch.pipeline().addLast(new Http2StreamFrameHandler(h2ClientHandlers)); + } + })); + // Handle connection-level frames (SETTINGS, PING, etc.) + pipeline.addLast(new Http2ConnectionFrameHandler()); + } else { + // HTTP/1.1 + pipeline.addLast(new HttpServerCodec()); + pipeline.addLast(new HttpObjectAggregator(1024 * 1024)); + pipeline.addLast(new Http11Handler(h11ClientHandlers)); + } + } + + public static class Http2ClientHandlers { + private final Http2ClientHandlerFactory factory; + private final Map h2ClientHandlers = new ConcurrentHashMap<>(); + + Http2ClientHandlers(Http2ClientHandlerFactory factory) { + this.factory = factory; + } + + public Http2ClientHandler create(ChannelHandlerContext ctx) { + var result = factory.create(ctx); + h2ClientHandlers.put(ctx.channel().id(), result); + return result; + } + + public Http2ClientHandler get(ChannelHandlerContext ctx) { + return h2ClientHandlers.get(ctx.channel().id()); + } + } + + public static class Http11ClientHandlers { + private final Http11ClientHandlerFactory factory; + private final Map h11ClientHandlers = new ConcurrentHashMap<>(); + + Http11ClientHandlers(Http11ClientHandlerFactory factory) { + this.factory = factory; + } + + public Http11ClientHandler create(ChannelHandlerContext ctx) { + var result = factory.create(ctx); + h11ClientHandlers.put(ctx.channel().id(), result); + return result; + } + + public Http11ClientHandler get(ChannelHandlerContext ctx) { + return h11ClientHandlers.get(ctx.channel().id()); + } + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TestCertificateGenerator.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TestCertificateGenerator.java new file mode 100644 index 000000000..ca146733b --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TestCertificateGenerator.java @@ -0,0 +1,137 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Security; +import java.security.cert.X509Certificate; +import java.util.Date; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.ExtendedKeyUsage; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.KeyPurposeId; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +public class TestCertificateGenerator { + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + public static CertificateBundle generateCertificates() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + + KeyPair caKeyPair = keyGen.generateKeyPair(); + X509Certificate caCert = generateCACertificate(caKeyPair); + + KeyPair serverKeyPair = keyGen.generateKeyPair(); + X509Certificate serverCert = generateServerCertificate(serverKeyPair, caKeyPair, caCert); + + return new CertificateBundle(caCert, serverCert, serverKeyPair.getPrivate()); + } + + private static X509Certificate generateCACertificate(KeyPair keyPair) throws Exception { + var issuer = new X500Name("CN=Test CA, O=Test, C=US"); + var serial = BigInteger.valueOf(System.currentTimeMillis()); + var notBefore = new Date(); + var notAfter = new Date(notBefore.getTime() + 365L * 24 * 60 * 60 * 1000); + + // Create extension utils for key identifiers + var extUtils = new JcaX509ExtensionUtils(); + var subjectKeyIdentifier = extUtils.createSubjectKeyIdentifier(keyPair.getPublic()); + + var certBuilder = new JcaX509v3CertificateBuilder( + issuer, + serial, + notBefore, + notAfter, + issuer, + keyPair.getPublic()) + .addExtension(Extension.basicConstraints, true, new BasicConstraints(true)) + .addExtension(Extension.keyUsage, + true, + new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign)) + .addExtension(Extension.subjectKeyIdentifier, false, subjectKeyIdentifier); + + var signer = new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate()); + var certHolder = certBuilder.build(signer); + + return new JcaX509CertificateConverter().getCertificate(certHolder); + } + + private static X509Certificate generateServerCertificate( + KeyPair serverKeyPair, + KeyPair caKeyPair, + X509Certificate caCert + ) throws Exception { + // Get the issuer directly from the CA cert to ensure exact match + var issuer = X500Name.getInstance(caCert.getSubjectX500Principal().getEncoded()); + var subject = new X500Name("CN=localhost, O=Test, C=US"); + var serial = BigInteger.valueOf(System.currentTimeMillis()); + var notBefore = new Date(); + var notAfter = new Date(notBefore.getTime() + 365L * 24 * 60 * 60 * 1000); + + var sanNames = new GeneralName[] { + new GeneralName(GeneralName.dNSName, "localhost"), + new GeneralName(GeneralName.iPAddress, "127.0.0.1") + }; + + // Create extension utils for key identifiers + var extUtils = new JcaX509ExtensionUtils(); + var subjectKeyIdentifier = extUtils.createSubjectKeyIdentifier(serverKeyPair.getPublic()); + var authorityKeyIdentifier = extUtils.createAuthorityKeyIdentifier(caCert.getPublicKey()); + + var certBuilder = new JcaX509v3CertificateBuilder( + issuer, + serial, + notBefore, + notAfter, + subject, + serverKeyPair.getPublic()) + .addExtension(Extension.subjectAlternativeName, false, new GeneralNames(sanNames)) + .addExtension(Extension.keyUsage, + true, + new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment)) + .addExtension(Extension.extendedKeyUsage, + false, + new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth)) + .addExtension(Extension.subjectKeyIdentifier, false, subjectKeyIdentifier) + .addExtension(Extension.authorityKeyIdentifier, false, authorityKeyIdentifier); + + var signer = new JcaContentSignerBuilder("SHA256withRSA").build(caKeyPair.getPrivate()); + var certHolder = certBuilder.build(signer); + + return new JcaX509CertificateConverter().getCertificate(certHolder); + } + + public static class CertificateBundle { + public final X509Certificate caCertificate; + public final X509Certificate serverCertificate; + public final PrivateKey serverPrivateKey; + + public CertificateBundle( + X509Certificate caCertificate, + X509Certificate serverCertificate, + PrivateKey serverPrivateKey + ) { + this.caCertificate = caCertificate; + this.serverCertificate = serverCertificate; + this.serverPrivateKey = serverPrivateKey; + } + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp11ClientHandler.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp11ClientHandler.java new file mode 100644 index 000000000..26e0f5de3 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp11ClientHandler.java @@ -0,0 +1,66 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.LastHttpContent; +import io.netty.util.CharsetUtil; + +public class TextResponseHttp11ClientHandler implements Http11ClientHandler { + private final String message; + + public TextResponseHttp11ClientHandler(String message) { + this.message = message; + } + + @Override + public void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request) { + sendResponse(ctx); + } + + @Override + public void onRequest(ChannelHandlerContext ctx, HttpRequest request) { + sendResponse(ctx); + } + + @Override + public void onContent(ChannelHandlerContext ctx, HttpContent content) { + + } + + @Override + public void onLastContent(ChannelHandlerContext ctx, LastHttpContent content) { + + } + + @Override + public void onException(ChannelHandlerContext ctx, Throwable cause) { + + } + + private void sendResponse(ChannelHandlerContext ctx) { + var content = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8); + var response = new DefaultFullHttpResponse( + HttpVersion.HTTP_1_1, + HttpResponseStatus.OK, + content); + var headers = response.headers(); + headers.set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); + headers.set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes()); + headers.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); + ctx.writeAndFlush(response); + } + +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java new file mode 100644 index 000000000..a0e490d2d --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; +import io.netty.handler.codec.http2.Http2DataFrame; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import io.netty.util.CharsetUtil; + +public class TextResponseHttp2ClientHandler implements Http2ClientHandler { + private static final NettyTestLogger LOGGER = NettyTestLogger.getLogger(TextResponseHttp2ClientHandler.class); + private final String message; + + public TextResponseHttp2ClientHandler(String message) { + this.message = message; + } + + @Override + public void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame) { + LOGGER.info(ctx.channel(), "headers received, sending response"); + var responseHeaders = new DefaultHttp2Headers(); + responseHeaders.status("200"); + responseHeaders.set("content-type", "text/plain"); + ctx.write(new DefaultHttp2HeadersFrame(responseHeaders, false)); + var content = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8); + ctx.writeAndFlush(new DefaultHttp2DataFrame(content, false)); + } + + @Override + public void onDataFrame(ChannelHandlerContext ctx, Http2DataFrame frame) { + LOGGER.info(ctx.channel(), "data frame received, closing response"); + if (frame.isEndStream()) { + ctx.writeAndFlush(new DefaultHttp2DataFrame(true)); + } + } + + @Override + public void onException(ChannelHandlerContext ctx, Throwable cause) { + + } +} From 47380d13ff8ddffbf5d32859aed292faa3c0e722 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 4 Dec 2025 14:09:00 -0600 Subject: [PATCH 03/60] Add SSLParameters to connection pool --- .../connection/HttpConnectionFactory.java | 26 ++++++++++++++++++- .../client/connection/HttpConnectionPool.java | 1 + .../connection/HttpConnectionPoolBuilder.java | 24 +++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java index 7553cfefd..c8fd80ae7 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java @@ -37,6 +37,7 @@ final class HttpConnectionFactory { private final Duration readTimeout; private final Duration writeTimeout; private final SSLContext sslContext; + private final SSLParameters sslParameters; // may be null private final HttpVersionPolicy versionPolicy; private final DnsResolver dnsResolver; private final HttpSocketFactory socketFactory; @@ -47,6 +48,7 @@ final class HttpConnectionFactory { Duration readTimeout, Duration writeTimeout, SSLContext sslContext, + SSLParameters sslParameters, HttpVersionPolicy versionPolicy, DnsResolver dnsResolver, HttpSocketFactory socketFactory @@ -56,6 +58,7 @@ final class HttpConnectionFactory { this.readTimeout = readTimeout; this.writeTimeout = writeTimeout; this.sslContext = sslContext; + this.sslParameters = sslParameters; this.versionPolicy = versionPolicy; this.dnsResolver = dnsResolver; this.socketFactory = socketFactory; @@ -116,8 +119,12 @@ private Socket performTlsHandshake(Socket socket, Route route) throws IOExceptio SSLSocket sslSocket = (SSLSocket) sslContext.getSocketFactory() .createSocket(socket, route.host(), route.port(), true); - SSLParameters params = sslSocket.getSSLParameters(); + // Start with custom params if provided, otherwise use socket defaults + SSLParameters params = sslParameters != null + ? copyParameters(sslParameters) + : sslSocket.getSSLParameters(); params.setEndpointIdentificationAlgorithm("HTTPS"); + // ALPN is always set based on version policy (overrides any custom setting) params.setApplicationProtocols(versionPolicy.alpnProtocols()); sslSocket.setSSLParameters(params); @@ -136,6 +143,23 @@ private Socket performTlsHandshake(Socket socket, Route route) throws IOExceptio } } + private static SSLParameters copyParameters(SSLParameters src) { + SSLParameters dst = new SSLParameters(); + dst.setCipherSuites(src.getCipherSuites()); + dst.setProtocols(src.getProtocols()); + dst.setWantClientAuth(src.getWantClientAuth()); + dst.setNeedClientAuth(src.getNeedClientAuth()); + dst.setAlgorithmConstraints(src.getAlgorithmConstraints()); + dst.setEndpointIdentificationAlgorithm(src.getEndpointIdentificationAlgorithm()); + dst.setServerNames(src.getServerNames()); + dst.setSNIMatchers(src.getSNIMatchers()); + dst.setUseCipherSuitesOrder(src.getUseCipherSuitesOrder()); + dst.setEnableRetransmissions(src.getEnableRetransmissions()); + dst.setMaximumPacketSize(src.getMaximumPacketSize()); + dst.setApplicationProtocols(src.getApplicationProtocols()); + return dst; + } + private HttpConnection createProtocolConnection(Socket socket, Route route) throws IOException { String protocol = "http/1.1"; diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java index 425641a96..5a7767b99 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java @@ -180,6 +180,7 @@ public final class HttpConnectionPool implements ConnectionPool { builder.readTimeout, builder.writeTimeout, sslContext, + builder.sslParameters, builder.versionPolicy, dnsResolver, builder.socketFactory); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java index b4577c0c3..fd82e01a5 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java @@ -14,6 +14,7 @@ import java.util.Map; import java.util.Objects; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; import software.amazon.smithy.java.http.client.dns.DnsResolver; /** @@ -30,6 +31,7 @@ public final class HttpConnectionPoolBuilder { Duration readTimeout = Duration.ofSeconds(30); Duration writeTimeout = Duration.ofSeconds(30); SSLContext sslContext; + SSLParameters sslParameters; HttpVersionPolicy versionPolicy = HttpVersionPolicy.AUTOMATIC; DnsResolver dnsResolver; HttpSocketFactory socketFactory = HttpSocketFactory::defaultSocketFactory; @@ -289,6 +291,28 @@ public HttpConnectionPoolBuilder sslContext(SSLContext context) { return this; } + /** + * Set SSL parameters for HTTPS connections (default: derived from SSLContext). + * + *

Configure custom SSLParameters for: + *

    + *
  • Specific TLS protocol versions (e.g., TLSv1.3 only)
  • + *
  • Custom cipher suites
  • + *
  • SNI configuration
  • + *
  • Client authentication requirements
  • + *
+ * + *

Note: ALPN protocols are set automatically based on {@link #httpVersionPolicy} + * and will override any ALPN settings in the provided parameters. + * + * @param parameters the SSL parameters to use + * @return this builder + */ + public HttpConnectionPoolBuilder sslParameters(SSLParameters parameters) { + this.sslParameters = parameters; + return this; + } + /** * Set HTTP version policy to control which protocol versions are negotiated via ALPN (default: AUTOMATIC). * From d9efa712366dec8c9d4cd3fff511d41fd0071c13 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 4 Dec 2025 17:33:53 -0600 Subject: [PATCH 04/60] Add better proxy support --- .../java/http/client/DefaultHttpClient.java | 33 ++- .../smithy/java/http/client/HttpClient.java | 37 ++-- .../java/http/client/HttpCredentials.java | 69 +++++++ .../java/http/client/ProxyConfiguration.java | 194 ++---------------- .../java/http/client/ProxySelector.java | 91 ++++++++ .../connection/H1ConnectionManager.java | 6 +- .../connection/HttpConnectionFactory.java | 22 +- .../java/http/client/connection/Route.java | 10 +- .../java/http/client/h1/H1Connection.java | 140 ------------- .../java/http/client/h1/ProxyTunnel.java | 100 +++++++++ 10 files changed, 347 insertions(+), 355 deletions(-) create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpCredentials.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxySelector.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ProxyTunnel.java diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java index c0541d9f9..324b1654c 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.OutputStream; +import java.net.URI; import java.time.Duration; import java.util.List; import java.util.concurrent.ExecutionException; @@ -29,7 +30,7 @@ final class DefaultHttpClient implements HttpClient { private final ConnectionPool connectionPool; - private final ProxyConfiguration proxyConfiguration; + private final ProxySelector proxySelector; private final List interceptors; private final Duration requestTimeout; private final ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor(); @@ -37,7 +38,7 @@ final class DefaultHttpClient implements HttpClient { DefaultHttpClient(Builder builder) { this.connectionPool = builder.connectionPool; this.interceptors = List.copyOf(builder.interceptors); - this.proxyConfiguration = builder.proxyConfiguration; + this.proxySelector = builder.proxySelector; this.requestTimeout = builder.requestTimeout; } @@ -123,7 +124,33 @@ private HttpExchange createManagedExchange( Context context, List resolvedInterceptors ) throws IOException { - Route route = Route.from(request.uri(), proxyConfiguration); + URI target = request.uri(); + List proxies = proxySelector.select(target, context); + + if (proxies.isEmpty()) { + return createManagedExchangeForRoute(request, context, resolvedInterceptors, Route.from(target, null)); + } + + IOException last = null; + for (ProxyConfiguration proxy : proxies) { + Route route = Route.from(target, proxy); + try { + return createManagedExchangeForRoute(request, context, resolvedInterceptors, route); + } catch (IOException e) { + last = e; + proxySelector.connectFailed(target, context, proxy, e); + } + } + + throw last; + } + + private HttpExchange createManagedExchangeForRoute( + HttpRequest request, + Context context, + List resolvedInterceptors, + Route route + ) throws IOException { HttpConnection conn = connectionPool.acquire(route); try { HttpExchange baseExchange = conn.newExchange(request); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpClient.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpClient.java index 1983dca57..2806851c5 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpClient.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpClient.java @@ -118,7 +118,7 @@ final class Builder { ConnectionPool connectionPool; Duration requestTimeout; final Deque interceptors = new ArrayDeque<>(); - ProxyConfiguration proxyConfiguration; + ProxySelector proxySelector = ProxySelector.direct(); private Builder() {} @@ -189,44 +189,33 @@ public Builder requestTimeout(Duration timeout) { /** * Set proxy configuration for all connections made by this client. * - *

When configured, all HTTP requests will be routed through the proxy - * unless the target host matches one of the non-proxy hosts. + *

When configured, all HTTP requests will be routed through the proxy unless the target host matches + * one of the non-proxy hosts. * - *

For HTTPS requests, the client establishes a CONNECT tunnel through - * the proxy, then performs TLS handshake through the tunnel. + *

For HTTPS requests, the client establishes a CONNECT tunnel through the proxy, then performs TLS + * handshake through the tunnel. * - *

For HTTP requests, the client connects to the proxy and sends - * requests with absolute URIs. + *

For HTTP requests, the client connects to the proxy and sends requests with absolute URIs. * * @param proxy the proxy configuration, or null for direct connections * @return this builder * @see ProxyConfiguration */ public Builder proxy(ProxyConfiguration proxy) { - this.proxyConfiguration = proxy; - return this; + return proxySelector(proxy != null ? ProxySelector.of(proxy) : ProxySelector.direct()); } /** - * Set proxy configuration using a URI string. + * Set a custom proxy selector for dynamic proxy selection. * - *

Convenience method that creates an HTTP proxy configuration from - * a URI string. For more advanced configuration (authentication, - * bypass rules, SOCKS proxy), use {@link #proxy(ProxyConfiguration)}. + *

The selector is called for each request and can return multiple proxies to try in order. + * If a proxy fails, the next one is attempted. * - * @param proxyUri the proxy URI (e.g., {@code "http://proxy.example.com:8080"}) + * @param selector the proxy selector to use * @return this builder - * @throws IllegalArgumentException if proxyUri is invalid */ - public Builder proxy(String proxyUri) { - if (proxyUri == null) { - this.proxyConfiguration = null; - } else { - this.proxyConfiguration = ProxyConfiguration.builder() - .proxyUri(proxyUri) - .type(ProxyConfiguration.ProxyType.HTTP) - .build(); - } + public Builder proxySelector(ProxySelector selector) { + this.proxySelector = Objects.requireNonNull(selector, "proxySelector"); return this; } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpCredentials.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpCredentials.java new file mode 100644 index 000000000..b932c8c08 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpCredentials.java @@ -0,0 +1,69 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; +import java.util.Objects; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; + +/** + * Credentials for HTTP authentication. + * + *

Implementations handle both preemptive auth (e.g., Basic, Bearer) and challenge-response auth (e.g., + * Digest, NTLM, Negotiate). + * + *

Used for both proxy authentication (CONNECT requests) and server authentication (normal requests). + */ +public interface HttpCredentials { + /** + * Apply authentication to an HTTP request. + * + *

Called before sending the request (preemptive), and again if a 401/407 challenge is received (reactive). + * Implementations can handle multi-round handshakes by tracking state internally. + * + * @param request the request builder to add auth headers to + * @param priorResponse null on first call, or the 401/407 response for reactive auth + * @return true if auth was applied and should retry, false to give up + */ + boolean authenticate(HttpRequest.Builder request, HttpResponse priorResponse); + + /** + * HTTP Basic authentication credentials. + * + *

Sends credentials preemptively in the Authorization or Proxy-Authorization header. + */ + record Basic(String username, String password, boolean forProxy) implements HttpCredentials { + + public Basic { + Objects.requireNonNull(username, "username"); + Objects.requireNonNull(password, "password"); + } + + /** + * Create Basic credentials for server authentication. + */ + public Basic(String username, String password) { + this(username, password, false); + } + + @Override + public boolean authenticate(HttpRequest.Builder request, HttpResponse priorResponse) { + // Basic auth is preemptive. If we already tried and got a challenge, give up. + if (priorResponse != null) { + return false; + } + + String credentials = Base64.getEncoder() + .encodeToString((username + ':' + password).getBytes(StandardCharsets.UTF_8)); + String header = forProxy ? "Proxy-Authorization" : "Authorization"; + request.withReplacedHeader(header, List.of("Basic " + credentials)); + return true; + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxyConfiguration.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxyConfiguration.java index fed65987c..cc9861641 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxyConfiguration.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxyConfiguration.java @@ -6,8 +6,6 @@ package software.amazon.smithy.java.http.client; import java.net.URI; -import java.util.List; -import java.util.Locale; import java.util.Objects; /** @@ -17,50 +15,35 @@ * * @param proxyUri Proxy server URI. * @param type Type of proxy. - * @param username Optional username for proxy authentication. - * @param password Optional password for proxy authentication. - * @param nonProxyHosts Hosts that should bypass the proxy. Supports wildcards: "*.internal.example.com". + * @param credentials Optional credentials for proxy authentication. */ -public record ProxyConfiguration( - URI proxyUri, - ProxyType type, - String username, - String password, - List nonProxyHosts) { +public record ProxyConfiguration(URI proxyUri, ProxyType type, HttpCredentials credentials) { + /** + * Create a proxy configuration without authentication. + * + * @param proxyUri proxy server URI + * @param type proxy type + */ + public ProxyConfiguration(URI proxyUri, ProxyType type) { + this(proxyUri, type, null); + } + public ProxyConfiguration { Objects.requireNonNull(proxyUri, "proxyUri cannot be null"); Objects.requireNonNull(type, "type cannot be null"); - nonProxyHosts = nonProxyHosts == null - ? List.of() - : nonProxyHosts.stream().map(s -> s.toLowerCase(Locale.ROOT)).toList(); } /** - * Check if a given host should bypass this proxy. + * Create a proxy configuration with Basic authentication. * - * @param host hostname to check - * @return true if host should bypass proxy + * @param proxyUri proxy server URI + * @param type proxy type + * @param username authentication username + * @param password authentication password + * @return proxy configuration with Basic auth credentials */ - public boolean shouldBypass(String host) { - if (host == null || nonProxyHosts.isEmpty()) { - return false; - } - String lowerHost = host.toLowerCase(Locale.ROOT); - for (String pattern : nonProxyHosts) { - if (matchesPattern(lowerHost, pattern)) { - return true; - } - } - return false; - } - - private boolean matchesPattern(String host, String pattern) { - // Simple wildcard matching (host and pattern are already lowercase) - if (pattern.startsWith("*.")) { - String suffix = pattern.substring(1); // Remove '*', keep the dot - return host.endsWith(suffix); - } - return host.equals(pattern); + public static ProxyConfiguration withBasicAuth(URI proxyUri, ProxyType type, String username, String password) { + return new ProxyConfiguration(proxyUri, type, new HttpCredentials.Basic(username, password, true)); } /** @@ -85,156 +68,23 @@ public int port() { if (port != -1) { return port; } - // Default ports return switch (type) { - case HTTP, HTTPS -> 8080; + case HTTP -> 8080; case SOCKS4, SOCKS5 -> 1080; }; } - /** - * Returns whether proxy authentication is configured. - * - * @return true if username is set - */ - public boolean requiresAuth() { - return username != null; - } - /** * Proxy protocol type. */ public enum ProxyType { - /** HTTP proxy (CONNECT tunnel for HTTPS) */ + /** HTTP proxy (CONNECT tunnel for HTTPS targets) */ HTTP, - /** HTTPS proxy */ - HTTPS, - /** SOCKS4 proxy */ SOCKS4, /** SOCKS5 proxy */ SOCKS5 } - - /** - * Builder for ProxyConfiguration. - */ - public static Builder builder() { - return new Builder(); - } - - public static final class Builder { - private URI proxyUri; - private ProxyType type = ProxyType.HTTP; - private String username; - private String password; - private List nonProxyHosts = List.of(); - - private Builder() {} - - /** - * Sets the proxy server URI. - * - * @param proxyUri the proxy URI (e.g., {@code http://proxy.example.com:8080}) - * @return this builder - */ - public Builder proxyUri(URI proxyUri) { - this.proxyUri = proxyUri; - return this; - } - - /** - * Sets the proxy server URI from a string. - * - * @param proxyUri the proxy URI string - * @return this builder - * @throws IllegalArgumentException if the URI is invalid - */ - public Builder proxyUri(String proxyUri) { - return proxyUri(URI.create(proxyUri)); - } - - /** - * Sets the proxy type (default: HTTP). - * - * @param type the proxy protocol type - * @return this builder - */ - public Builder type(ProxyType type) { - this.type = type; - return this; - } - - /** - * Sets the username for proxy authentication. - * - * @param username the authentication username - * @return this builder - */ - public Builder username(String username) { - this.username = username; - return this; - } - - /** - * Sets the password for proxy authentication. - * - * @param password the authentication password - * @return this builder - */ - public Builder password(String password) { - this.password = password; - return this; - } - - /** - * Sets both username and password for proxy authentication. - * - * @param username the authentication username - * @param password the authentication password - * @return this builder - */ - public Builder credentials(String username, String password) { - this.username = username; - this.password = password; - return this; - } - - /** - * Sets hosts that should bypass the proxy. - * - *

Supports wildcard patterns: {@code *.internal.example.com} matches - * any subdomain of {@code internal.example.com}. - * - * @param nonProxyHosts list of hostnames or patterns to bypass - * @return this builder - */ - public Builder nonProxyHosts(List nonProxyHosts) { - this.nonProxyHosts = List.copyOf(nonProxyHosts); - return this; - } - - /** - * Sets hosts that should bypass the proxy. - * - * @param nonProxyHosts hostnames or patterns to bypass - * @return this builder - * @see #nonProxyHosts(List) - */ - public Builder nonProxyHosts(String... nonProxyHosts) { - return nonProxyHosts(List.of(nonProxyHosts)); - } - - /** - * Builds the proxy configuration. - * - * @return the configured ProxyConfiguration - * @throws NullPointerException if proxyUri is null - */ - public ProxyConfiguration build() { - return new ProxyConfiguration(proxyUri, type, username, password, nonProxyHosts); - } - } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxySelector.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxySelector.java new file mode 100644 index 000000000..21c26cdc2 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxySelector.java @@ -0,0 +1,91 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.List; +import software.amazon.smithy.java.context.Context; + +/** + * Selects proxies for HTTP requests. + * + *

Failover

+ *

ProxySelector implementations can return multiple {@link ProxyConfiguration} objects. + * Implementations will try to connect to each proxy, one after the other, until a connection can be established. + * To prevent proxy failover, return only a single result using {@link #noFailover(ProxySelector)}. + * + *

Implementations must be thread-safe. + */ +public interface ProxySelector { + /** + * Returns an ordered list of proxies to try for the given request. + * + *

An empty list means "connect directly". + * + * @param target the target URI of the request + * @param context the Context for the request + * @return ordered list of proxies to try (may be empty, never null) + */ + List select(URI target, Context context); + + /** + * Notifies the selector that a connection via the given proxy failed. + * + *

Implementations can use this to update health / backoff state. + * + * @param target the original request target + * @param context the Context for the request + * @param proxy the proxy that failed + * @param cause the IOException that occurred + */ + default void connectFailed(URI target, Context context, ProxyConfiguration proxy, IOException cause) { + // default no-op + } + + /** + * Returns a ProxySelector that always uses the given proxy configurations in order. + * + * @param config proxy configurations + * @return the created ProxySelector. + */ + static ProxySelector of(ProxyConfiguration... config) { + var result = List.of(config); + return (target, context) -> result; + } + + /** + * Returns a ProxySelector that never uses a proxy. + * + * @return the direct proxy. + */ + static ProxySelector direct() { + return (target, context) -> Collections.emptyList(); + } + + /** + * Returns a ProxySelector that takes the first result of the selector to prevent failover. + * + * @param delegate Delegate selector to wrap. + * @return the ProxySelector that does not use failover. + */ + static ProxySelector noFailover(ProxySelector delegate) { + java.net.ProxySelector.getDefault(); + return new ProxySelector() { + @Override + public List select(URI target, Context ctx) { + var proxies = delegate.select(target, ctx); + return proxies.isEmpty() ? proxies : List.of(proxies.getFirst()); + } + + @Override + public void connectFailed(URI target, Context context, ProxyConfiguration proxy, IOException cause) { + delegate.connectFailed(target, context, proxy, cause); + } + }; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java index 111de0db0..020847cbf 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java @@ -37,7 +37,7 @@ final class H1ConnectionManager { * Try to acquire a pooled connection for the route. * * @param route the route - * @param maxConnections function to get max connections for route (called lazily) + * @param poolFactory * @return a valid pooled connection, or null if none available */ PooledConnection tryAcquire(Route route, IntFunction poolFactory) { @@ -184,7 +184,9 @@ int removeIdleConnections(long maxIdleNanos, BiConsumerProxy routing: * {@snippet : - * ProxyConfiguration proxy = ProxyConfiguration.builder() - * .proxyUri("http://proxy.corp.com:8080") - * .type(ProxyType.HTTP) - * .build(); + * ProxyConfiguration proxy = new ProxyConfiguration(URI.create("http://proxy.corp.com:8080"), ProxyType.HTTP); * * Route directRoute = Route.from(uri); * Route proxiedRoute = Route.from(uri, proxy); @@ -204,11 +201,6 @@ public static Route from(URI uri, ProxyConfiguration proxy) { port = "https".equals(scheme) ? 443 : 80; } - // Check if this host should bypass proxy - if (proxy != null && proxy.shouldBypass(host)) { - proxy = null; - } - return new Route(scheme, host, port, proxy); } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Connection.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Connection.java index 0b30a4ccf..10558e99c 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Connection.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Connection.java @@ -5,22 +5,15 @@ package software.amazon.smithy.java.http.client.h1; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; import java.net.Socket; -import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.Base64; import java.util.concurrent.atomic.AtomicBoolean; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import software.amazon.smithy.java.http.api.HttpRequest; import software.amazon.smithy.java.http.api.HttpVersion; import software.amazon.smithy.java.http.client.HttpExchange; -import software.amazon.smithy.java.http.client.ProxyConfiguration; import software.amazon.smithy.java.http.client.UnsyncBufferedInputStream; import software.amazon.smithy.java.http.client.UnsyncBufferedOutputStream; import software.amazon.smithy.java.http.client.connection.HttpConnection; @@ -260,137 +253,4 @@ void markInactive() { LOGGER.debug("Marking connection inactive to {}", route); this.active = false; } - - /** - * Establish an HTTP CONNECT tunnel through a proxy. - * - *

This is a static factory method that performs the proxy handshake - * and returns a connected socket ready for use. The CONNECT tunnel is - * only needed for HTTPS through an HTTP proxy. - * - *

For HTTP through a proxy, no tunnel is needed - requests are sent - * with absolute URIs directly to the proxy. - * - *

CONNECT Protocol

- *
-     * Client → Proxy:
-     *   CONNECT api.example.com:443 HTTP/1.1
-     *   Host: api.example.com:443
-     *   Proxy-Authorization: Basic dXNlcjpwYXNz  (if auth required)
-     *
-     * Proxy → Client:
-     *   HTTP/1.1 200 Connection Established
-     * 
- * - *

After receiving 200, the socket is connected through the tunnel - * and TLS handshake can proceed as if connecting directly. - * - * @param proxySocket socket connected to proxy server - * @param targetHost target host for CONNECT request - * @param targetPort target port for CONNECT request - * @param proxy proxy configuration (for authentication) - * @throws IOException if CONNECT tunnel establishment fails - */ - public static void establishConnectTunnel( - Socket proxySocket, - String targetHost, - int targetPort, - ProxyConfiguration proxy - ) throws IOException { - - try { - OutputStream out = proxySocket.getOutputStream(); - InputStream in = proxySocket.getInputStream(); - - // Build CONNECT request - StringBuilder request = new StringBuilder(); - request.append("CONNECT ") - .append(targetHost) - .append(":") - .append(targetPort) - .append(" HTTP/1.1\r\n"); - request.append("Host: ") - .append(targetHost) - .append(":") - .append(targetPort) - .append("\r\n"); - - // Add proxy authentication if configured - if (proxy.requiresAuth()) { - String credentials = proxy.username() + ":" + proxy.password(); - String encoded = Base64.getEncoder() - .encodeToString( - credentials.getBytes(StandardCharsets.UTF_8)); - request.append("Proxy-Authorization: Basic ").append(encoded).append("\r\n"); - } - - // Keep proxy connection alive for reuse - request.append("Proxy-Connection: Keep-Alive\r\n"); - request.append("\r\n"); - - // Send CONNECT request - out.write(request.toString().getBytes(StandardCharsets.US_ASCII)); - out.flush(); - - // Read response status line - BufferedReader reader = new BufferedReader( - new InputStreamReader(in, StandardCharsets.US_ASCII)); - String statusLine = reader.readLine(); - - if (statusLine == null) { - throw new IOException("Proxy closed connection during CONNECT handshake"); - } - - // Parse status line: "HTTP/1.1 200 Connection Established" - String[] parts = statusLine.split("\\s+", 3); - if (parts.length < 2) { - throw new IOException("Invalid proxy response: " + statusLine); - } - - int statusCode; - try { - statusCode = Integer.parseInt(parts[1]); - } catch (NumberFormatException e) { - throw new IOException("Invalid status code in proxy response: " + statusLine); - } - - // Read and discard response headers until empty line - String line; - while ((line = reader.readLine()) != null && !line.isEmpty()) { - // Skip header lines - } - - // Check status code - if (statusCode == 200) { - // Tunnel established successfully - return; - } else if (statusCode == 407) { - // Proxy authentication required - throw new IOException("Proxy authentication required (407). Check proxy credentials."); - } else if (statusCode >= 400 && statusCode < 500) { - // Client error - throw new IOException( - "Proxy rejected CONNECT request: " + statusCode + " " + - (parts.length > 2 ? parts[2] : "")); - } else if (statusCode >= 500) { - // Server error - throw new IOException( - "Proxy server error during CONNECT: " + statusCode + " " + - (parts.length > 2 ? parts[2] : "")); - } else { - // Unexpected status code - throw new IOException("Unexpected proxy response: " + statusLine); - } - - } catch (IOException e) { - // Close socket on any error - try { - proxySocket.close(); - } catch (IOException ignored) {} - throw new IOException( - "Failed to establish CONNECT tunnel to " + targetHost + ":" + targetPort + - " through proxy", - e); - } - } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ProxyTunnel.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ProxyTunnel.java new file mode 100644 index 000000000..704e3bd40 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ProxyTunnel.java @@ -0,0 +1,100 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import java.io.IOException; +import java.net.Socket; +import java.net.URI; +import java.time.Duration; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.client.HttpCredentials; +import software.amazon.smithy.java.http.client.HttpExchange; +import software.amazon.smithy.java.http.client.connection.Route; + +/** + * Establishes HTTP CONNECT tunnels through proxies. + */ +public final class ProxyTunnel { + + private ProxyTunnel() {} + + /** + * Result of establishing a CONNECT tunnel through a proxy. + * + * @param socket the tunneled socket if successful, null if failed + * @param statusCode HTTP status code from proxy + * @param headers response headers from proxy + */ + public record Result(Socket socket, int statusCode, HttpHeaders headers) {} + + /** + * Connects to a proxy. + * + *

Performs the proxy handshake including authentication if credentials + * are provided. Supports multi-round auth protocols (e.g., NTLM, Negotiate). + * + * @param proxySocket socket connected to proxy server + * @param targetHost target host for CONNECT request + * @param targetPort target port for CONNECT request + * @param credentials optional credentials for proxy authentication + * @param readTimeout timeout for read operations + * @return tunnel result with socket (if successful) and response details + * @throws IOException if I/O error occurs during tunnel establishment + */ + public static Result establish( + Socket proxySocket, + String targetHost, + int targetPort, + HttpCredentials credentials, + Duration readTimeout + ) throws IOException { + Route proxyRoute = Route.direct( + "http", + proxySocket.getInetAddress().getHostAddress(), + proxySocket.getPort()); + H1Connection conn = new H1Connection(proxySocket, proxyRoute, readTimeout); + + HttpResponse priorResponse = null; + + do { + HttpRequest.Builder requestBuilder = HttpRequest.builder() + .method("CONNECT") + .uri(URI.create("http://" + targetHost + ":" + targetPort)) + .withAddedHeader("Host", targetHost + ":" + targetPort) + .withAddedHeader("Proxy-Connection", "Keep-Alive"); + + if (credentials != null) { + boolean applied = credentials.authenticate(requestBuilder, priorResponse); + if (!applied && priorResponse != null) { + break; + } + } + + HttpExchange exchange = conn.newExchange(requestBuilder.build()); + exchange.requestBody().close(); + + int status = exchange.responseStatusCode(); + HttpHeaders headers = exchange.responseHeaders(); + + if (status == 200) { + conn.releaseExchange(); + return new Result(proxySocket, status, headers); + } + + priorResponse = HttpResponse.builder() + .statusCode(status) + .headers(headers) + .build(); + + conn.releaseExchange(); + + } while (priorResponse.statusCode() == 407 && credentials != null); + + return new Result(null, priorResponse.statusCode(), priorResponse.headers()); + } +} From 062883464567295d04905d722bf188b3c4295ba4 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 4 Dec 2025 18:34:06 -0600 Subject: [PATCH 05/60] Add start of some tests --- .../http/client/BoundedInputStreamTest.java | 81 +++++ .../DelegatedClosingInputStreamTest.java | 51 +++ .../DelegatedClosingOutputStreamTest.java | 52 +++ .../http/client/Http2IntegrationTest.java | 296 ------------------ .../smithy/java/http/client/HttpBinTest.java | 2 + .../client/NonClosingOutputStreamTest.java | 63 ++++ .../client/UnsyncBufferedInputStreamTest.java | 89 ++++++ .../UnsyncBufferedOutputStreamTest.java | 90 ++++++ 8 files changed, 428 insertions(+), 296 deletions(-) create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/BoundedInputStreamTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStreamTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStreamTest.java delete mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/Http2IntegrationTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/NonClosingOutputStreamTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStreamTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStreamTest.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/BoundedInputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/BoundedInputStreamTest.java new file mode 100644 index 000000000..3af64fca8 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/BoundedInputStreamTest.java @@ -0,0 +1,81 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +class BoundedInputStreamTest { + + @Test + void readsExactlyBoundedBytes() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new BoundedInputStream(delegate, 3); + + assertEquals(1, stream.read()); + assertEquals(2, stream.read()); + assertEquals(3, stream.read()); + assertEquals(-1, stream.read()); + } + + @Test + void readArrayRespectsBound() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new BoundedInputStream(delegate, 3); + + byte[] buf = new byte[10]; + int n = stream.read(buf, 0, 10); + + assertEquals(3, n); + assertArrayEquals(new byte[] {1, 2, 3}, java.util.Arrays.copyOf(buf, n)); + } + + @Test + void availableRespectsBound() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new BoundedInputStream(delegate, 3); + + assertEquals(3, stream.available()); + } + + @Test + void skipRespectsBound() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new BoundedInputStream(delegate, 3); + + assertEquals(3, stream.skip(10)); + assertEquals(-1, stream.read()); + } + + @Test + void closeDrainsRemainingBytes() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new BoundedInputStream(delegate, 3); + + stream.read(); // read 1 byte + stream.close(); + + // Delegate should have been drained to byte 4 + assertEquals(4, delegate.read()); + } + + @Test + void throwsOnPrematureEof() { + var delegate = new ByteArrayInputStream(new byte[] {1, 2}); + var stream = new BoundedInputStream(delegate, 5); + + assertThrows(IOException.class, () -> { + while (stream.read() != -1) { + // drain + } + }); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStreamTest.java new file mode 100644 index 000000000..39af68181 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStreamTest.java @@ -0,0 +1,51 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; + +class DelegatedClosingInputStreamTest { + + @Test + void callsOnCloseCallback() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var closeCount = new AtomicInteger(0); + + var stream = new DelegatedClosingInputStream(delegate, closeCount::incrementAndGet); + stream.close(); + + assertEquals(1, closeCount.get()); + } + + @Test + void callsOnCloseOnlyOnce() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var closeCount = new AtomicInteger(0); + + var stream = new DelegatedClosingInputStream(delegate, closeCount::incrementAndGet); + stream.close(); + stream.close(); + stream.close(); + + assertEquals(1, closeCount.get()); + } + + @Test + void readsFromDelegate() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new DelegatedClosingInputStream(delegate, () -> {}); + + assertEquals(1, stream.read()); + assertEquals(2, stream.read()); + assertEquals(3, stream.read()); + assertEquals(-1, stream.read()); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStreamTest.java new file mode 100644 index 000000000..f161914df --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStreamTest.java @@ -0,0 +1,52 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; + +class DelegatedClosingOutputStreamTest { + + @Test + void callsOnCloseCallback() throws IOException { + var delegate = new ByteArrayOutputStream(); + var closeCount = new AtomicInteger(0); + + var stream = new DelegatedClosingOutputStream(delegate, closeCount::incrementAndGet); + stream.close(); + + assertEquals(1, closeCount.get()); + } + + @Test + void callsOnCloseOnlyOnce() throws IOException { + var delegate = new ByteArrayOutputStream(); + var closeCount = new AtomicInteger(0); + + var stream = new DelegatedClosingOutputStream(delegate, closeCount::incrementAndGet); + stream.close(); + stream.close(); + stream.close(); + + assertEquals(1, closeCount.get()); + } + + @Test + void writesToDelegate() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new DelegatedClosingOutputStream(delegate, () -> {}); + + stream.write(new byte[] {1, 2, 3}); + stream.flush(); + + assertArrayEquals(new byte[] {1, 2, 3}, delegate.toByteArray()); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/Http2IntegrationTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/Http2IntegrationTest.java deleted file mode 100644 index 025ac7a87..000000000 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/Http2IntegrationTest.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import io.netty.bootstrap.ServerBootstrap; -import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.handler.codec.http2.DefaultHttp2DataFrame; -import io.netty.handler.codec.http2.DefaultHttp2Headers; -import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; -import io.netty.handler.codec.http2.Http2DataFrame; -import io.netty.handler.codec.http2.Http2FrameCodecBuilder; -import io.netty.handler.codec.http2.Http2FrameStream; -import io.netty.handler.codec.http2.Http2Headers; -import io.netty.handler.codec.http2.Http2HeadersFrame; -import io.netty.handler.codec.http2.Http2ResetFrame; -import io.netty.handler.codec.http2.Http2SecurityUtil; -import io.netty.handler.ssl.ApplicationProtocolConfig; -import io.netty.handler.ssl.ApplicationProtocolNames; -import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.ssl.SupportedCipherSuiteFilter; -import io.netty.handler.ssl.util.SelfSignedCertificate; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpRequest; -import software.amazon.smithy.java.http.api.HttpResponse; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.dns.DnsResolver; - -/** - * Integration test for HTTP/2 client implementation. - */ -public class Http2IntegrationTest { - - private static final byte[] CONTENT = "{\"status\":\"ok\"}".getBytes(StandardCharsets.UTF_8); - - private EventLoopGroup bossGroup; - private EventLoopGroup workerGroup; - private Channel serverChannel; - private String baseUrl; - private HttpClient client; - - @BeforeEach - void setup() throws Exception { - bossGroup = new NioEventLoopGroup(1); - workerGroup = new NioEventLoopGroup(4); - - // Start HTTP/2 server - SelfSignedCertificate ssc = new SelfSignedCertificate("localhost"); - SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) - .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE) - .applicationProtocolConfig(new ApplicationProtocolConfig( - ApplicationProtocolConfig.Protocol.ALPN, - ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, - ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, - ApplicationProtocolNames.HTTP_2)) - .build(); - - ServerBootstrap b = new ServerBootstrap(); - b.group(bossGroup, workerGroup) - .channel(NioServerSocketChannel.class) - .childHandler(new ChannelInitializer() { - @Override - public void initChannel(SocketChannel ch) { - ch.pipeline().addLast(sslCtx.newHandler(ch.alloc())); - ch.pipeline().addLast(new Http2AlpnHandler()); - } - }); - - serverChannel = b.bind(0).sync().channel(); - int port = ((InetSocketAddress) serverChannel.localAddress()).getPort(); - baseUrl = "https://localhost:" + port; - System.out.println("Started HTTP/2 test server on port " + port); - - // Create trust-all SSL context - TrustManager[] trustAllCerts = new TrustManager[] { - new X509TrustManager() { - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return new java.security.cert.X509Certificate[0]; - } - - public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {} - - public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {} - } - }; - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); - - // Create HTTP/2 client - DnsResolver staticDns = DnsResolver.staticMapping(Map.of( - "localhost", - List.of(InetAddress.getLoopbackAddress()))); - - client = HttpClient.builder() - .connectionPool(HttpConnectionPool.builder() - .maxConnectionsPerRoute(10) - .maxTotalConnections(10) - .maxIdleTime(Duration.ofMinutes(1)) - .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_2) - .dnsResolver(staticDns) - .sslContext(sslContext) - .build()) - .build(); - } - - @AfterEach - void teardown() throws Exception { - if (client != null) - client.close(); - if (serverChannel != null) - serverChannel.close().sync(); - if (bossGroup != null) - bossGroup.shutdownGracefully().sync(); - if (workerGroup != null) - workerGroup.shutdownGracefully().sync(); - } - - @Test - void testSimpleGet() throws Exception { - URI uri = URI.create(baseUrl + "/test"); - HttpRequest request = HttpRequest.builder() - .uri(uri) - .method("GET") - .build(); - - System.out.println("Sending HTTP/2 GET request to " + uri); - HttpResponse response = client.send(request); - - System.out.println("Response status: " + response.statusCode()); - byte[] bodyBytes = response.body().asByteBuffer().array(); - String bodyStr = new String(bodyBytes, StandardCharsets.UTF_8); - System.out.println("Response body: " + bodyStr); - - assertEquals(200, response.statusCode()); - assertEquals("{\"status\":\"ok\"}", bodyStr); - } - - @Test - void testSequentialMultiplexedRequests() throws Exception { - // Test sequential requests on same connection to verify basic multiplexing - URI uri = URI.create(baseUrl + "/test"); - - for (int i = 0; i < 3; i++) { - HttpRequest request = HttpRequest.builder() - .uri(uri) - .method("GET") - .build(); - - System.out.println("Sending sequential request " + i); - HttpResponse response = client.send(request); - assertEquals(200, response.statusCode(), "Request " + i + " should succeed"); - System.out.println("Sequential request " + i + " succeeded"); - } - } - - @Test - void testConcurrentMultiplexedRequests() throws Exception { - URI uri = URI.create(baseUrl + "/test"); - int concurrency = 3; // Start with small number to debug - - var errors = new java.util.concurrent.atomic.AtomicInteger(0); - var success = new java.util.concurrent.atomic.AtomicInteger(0); - var latch = new java.util.concurrent.CountDownLatch(concurrency); - - try (var executor = java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor()) { - for (int i = 0; i < concurrency; i++) { - final int reqNum = i; - executor.submit(() -> { - try { - HttpRequest request = HttpRequest.builder() - .uri(uri) - .method("GET") - .build(); - - HttpResponse response = client.send(request); - - if (response.statusCode() == 200) { - success.incrementAndGet(); - } else { - System.err.println("Request " + reqNum + " got status: " + response.statusCode()); - errors.incrementAndGet(); - } - } catch (Exception e) { - System.err.println("Request " + reqNum + " failed: " + e.getMessage()); - e.printStackTrace(); - errors.incrementAndGet(); - } finally { - latch.countDown(); - } - }); - } - - latch.await(30, java.util.concurrent.TimeUnit.SECONDS); - } - - System.out.println("Success: " + success.get() + ", Errors: " + errors.get()); - assertEquals(concurrency, success.get(), "All requests should succeed"); - assertEquals(0, errors.get(), "No errors should occur"); - } - - /** - * ALPN handler for HTTP/2. - */ - private static class Http2AlpnHandler extends ApplicationProtocolNegotiationHandler { - Http2AlpnHandler() { - super(ApplicationProtocolNames.HTTP_2); - } - - @Override - protected void configurePipeline(ChannelHandlerContext ctx, String protocol) { - System.out.println("Server negotiated protocol: " + protocol); - if (ApplicationProtocolNames.HTTP_2.equals(protocol)) { - ctx.pipeline() - .addLast( - Http2FrameCodecBuilder.forServer().build(), - new Http2Handler()); - } else { - throw new IllegalStateException("Unknown protocol: " + protocol); - } - } - } - - /** - * HTTP/2 request handler. - */ - private static class Http2Handler extends ChannelInboundHandlerAdapter { - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) { - if (msg instanceof Http2HeadersFrame headersFrame) { - System.out.println("Server received HEADERS on stream " + headersFrame.stream().id() + - ", endStream=" + headersFrame.isEndStream()); - if (headersFrame.isEndStream()) { - sendResponse(ctx, headersFrame.stream()); - } - } else if (msg instanceof Http2DataFrame dataFrame) { - System.out.println("Server received DATA on stream " + dataFrame.stream().id() + - ", endStream=" + dataFrame.isEndStream()); - dataFrame.release(); - if (dataFrame.isEndStream()) { - sendResponse(ctx, dataFrame.stream()); - } - } else if (msg instanceof Http2ResetFrame resetFrame) { - System.out.println("Server received RST_STREAM on stream " + resetFrame.stream().id() + - ", errorCode=" + resetFrame.errorCode()); - } else { - System.out.println("Server received unknown frame: " + msg.getClass().getSimpleName()); - } - } - - private void sendResponse(ChannelHandlerContext ctx, Http2FrameStream stream) { - System.out.println("Server sending response on stream " + stream.id()); - Http2Headers headers = new DefaultHttp2Headers() - .status("200") - .set("content-type", "application/json") - .setInt("content-length", CONTENT.length); - ctx.write(new DefaultHttp2HeadersFrame(headers).stream(stream)); - ctx.writeAndFlush(new DefaultHttp2DataFrame( - Unpooled.wrappedBuffer(CONTENT), - true).stream(stream)); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - System.err.println("Server error: " + cause.getMessage()); - cause.printStackTrace(); - ctx.close(); - } - } -} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpBinTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpBinTest.java index 6f4d81854..4a25363d0 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpBinTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpBinTest.java @@ -20,6 +20,7 @@ import java.util.zip.GZIPInputStream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import software.amazon.smithy.java.http.api.HttpRequest; import software.amazon.smithy.java.http.api.HttpResponse; @@ -32,6 +33,7 @@ * This test class validates the HTTP client against real-world scenarios using httpbin.org, * a free HTTP testing service. */ +@Disabled("Integration test - requires external service") class HttpBinTest { private static final String HTTPBIN_URL = "https://httpbin.org"; diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/NonClosingOutputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/NonClosingOutputStreamTest.java new file mode 100644 index 000000000..d80afa378 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/NonClosingOutputStreamTest.java @@ -0,0 +1,63 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; + +class NonClosingOutputStreamTest { + + @Test + void doesNotCloseDelegate() throws IOException { + var delegateClosed = new AtomicInteger(0); + var delegate = new ByteArrayOutputStream() { + @Override + public void close() { + delegateClosed.incrementAndGet(); + } + }; + + var stream = new NonClosingOutputStream(delegate); + stream.write(new byte[] {1, 2, 3}); + stream.close(); + + assertEquals(0, delegateClosed.get()); + assertArrayEquals(new byte[] {1, 2, 3}, delegate.toByteArray()); + } + + @Test + void flushesOnClose() throws IOException { + var flushCount = new AtomicInteger(0); + var delegate = new ByteArrayOutputStream() { + @Override + public void flush() { + flushCount.incrementAndGet(); + } + }; + + var stream = new NonClosingOutputStream(delegate); + stream.close(); + + assertTrue(flushCount.get() >= 1); + } + + @Test + void throwsAfterClose() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new NonClosingOutputStream(delegate); + stream.close(); + + assertThrows(IOException.class, () -> stream.write(1)); + assertThrows(IOException.class, () -> stream.write(new byte[] {1, 2, 3}, 0, 3)); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStreamTest.java new file mode 100644 index 000000000..8ea3e723d --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStreamTest.java @@ -0,0 +1,89 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +class UnsyncBufferedInputStreamTest { + + @Test + void readsSingleBytes() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + assertEquals(1, stream.read()); + assertEquals(2, stream.read()); + assertEquals(3, stream.read()); + assertEquals(-1, stream.read()); + } + + @Test + void readsIntoArray() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + byte[] buf = new byte[3]; + assertEquals(3, stream.read(buf)); + assertArrayEquals(new byte[] {1, 2, 3}, buf); + } + + @Test + void skipsBytes() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + assertEquals(2, stream.skip(2)); + assertEquals(3, stream.read()); + } + + @Test + void transfersToOutputStream() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + var out = new ByteArrayOutputStream(); + + assertEquals(5, stream.transferTo(out)); + assertArrayEquals(new byte[] {1, 2, 3, 4, 5}, out.toByteArray()); + } + + @Test + void readLineReturnsLine() throws IOException { + var data = "Hello\r\nWorld\n".getBytes(StandardCharsets.US_ASCII); + var delegate = new ByteArrayInputStream(data); + var stream = new UnsyncBufferedInputStream(delegate, 64); + + byte[] buf = new byte[64]; + int len = stream.readLine(buf, 64); + assertEquals("Hello", new String(buf, 0, len, StandardCharsets.US_ASCII)); + + len = stream.readLine(buf, 64); + assertEquals("World", new String(buf, 0, len, StandardCharsets.US_ASCII)); + } + + @Test + void throwsAfterClose() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + stream.close(); + + assertThrows(IOException.class, stream::read); + } + + @Test + void throwsOnInvalidBufferSize() { + var delegate = new ByteArrayInputStream(new byte[] {}); + assertThrows(IllegalArgumentException.class, + () -> new UnsyncBufferedInputStream(delegate, 0)); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStreamTest.java new file mode 100644 index 000000000..e98dc9c67 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStreamTest.java @@ -0,0 +1,90 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +class UnsyncBufferedOutputStreamTest { + + @Test + void writesSingleBytes() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new UnsyncBufferedOutputStream(delegate, 8); + + stream.write(1); + stream.write(2); + stream.write(3); + stream.flush(); + + assertArrayEquals(new byte[] {1, 2, 3}, delegate.toByteArray()); + } + + @Test + void writesArray() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new UnsyncBufferedOutputStream(delegate, 8); + + stream.write(new byte[] {1, 2, 3, 4, 5}); + stream.flush(); + + assertArrayEquals(new byte[] {1, 2, 3, 4, 5}, delegate.toByteArray()); + } + + @Test + void writesAsciiString() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new UnsyncBufferedOutputStream(delegate, 8); + + stream.writeAscii("Hello"); + stream.flush(); + + assertEquals("Hello", delegate.toString()); + } + + @Test + void flushesOnClose() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new UnsyncBufferedOutputStream(delegate, 8); + + stream.write(new byte[] {1, 2, 3}); + stream.close(); + + assertArrayEquals(new byte[] {1, 2, 3}, delegate.toByteArray()); + } + + @Test + void throwsAfterClose() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new UnsyncBufferedOutputStream(delegate, 8); + stream.close(); + + assertThrows(IOException.class, () -> stream.write(1)); + } + + @Test + void throwsOnInvalidBufferSize() { + var delegate = new ByteArrayOutputStream(); + assertThrows(IllegalArgumentException.class, + () -> new UnsyncBufferedOutputStream(delegate, 0)); + } + + @Test + void largeWriteBypassesBuffer() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new UnsyncBufferedOutputStream(delegate, 4); + + stream.write(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); + stream.flush(); + + assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}, delegate.toByteArray()); + } +} From 226f84eac4f79286a7e30a4ddfeb084147b0503e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Sugawara=20=28=E2=88=A9=EF=BD=80-=C2=B4=29?= =?UTF-8?q?=E2=8A=83=E2=94=81=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E?= Date: Thu, 4 Dec 2025 17:33:01 -0800 Subject: [PATCH 06/60] Update Netty version --- http/http-client/build.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/http/http-client/build.gradle.kts b/http/http-client/build.gradle.kts index 30f000766..c4ccc9105 100644 --- a/http/http-client/build.gradle.kts +++ b/http/http-client/build.gradle.kts @@ -26,7 +26,7 @@ dependencies { api(project(":logging")) // Netty for HTTP/2 integration tests - testImplementation("io.netty:netty-all:4.1.100.Final") + testImplementation("io.netty:netty-all:4.2.7.Final") testImplementation("org.bouncycastle:bcpkix-jdk18on:1.78.1") // Add Apache HttpClient for benchmarking comparison @@ -37,10 +37,10 @@ dependencies { jmh("io.helidon.webclient:helidon-webclient-http2:4.1.6") // Netty for raw HTTP/2 benchmarking - jmh("io.netty:netty-all:4.1.100.Final") + jmh("io.netty:netty-all:4.2.7.Final") // Benchmark server dependencies (Netty runs in separate process) - jmhServerImplementation("io.netty:netty-all:4.1.100.Final") + jmhServerImplementation("io.netty:netty-all:4.2.7.Final") jmhServerImplementation("org.bouncycastle:bcpkix-jdk18on:1.78.1") } From 648d55b98f2736f6058de5ffc5e199faa3be73d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Sugawara=20=28=E2=88=A9=EF=BD=80-=C2=B4=29?= =?UTF-8?q?=E2=8A=83=E2=94=81=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E?= Date: Thu, 4 Dec 2025 17:41:40 -0800 Subject: [PATCH 07/60] End the stream if the headers is marked as end of stream --- .../client/it/server/TextResponseHttp2ClientHandler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java index a0e490d2d..b152e2ad4 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java @@ -24,13 +24,14 @@ public TextResponseHttp2ClientHandler(String message) { @Override public void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame) { - LOGGER.info(ctx.channel(), "headers received, sending response"); var responseHeaders = new DefaultHttp2Headers(); responseHeaders.status("200"); responseHeaders.set("content-type", "text/plain"); ctx.write(new DefaultHttp2HeadersFrame(responseHeaders, false)); var content = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8); - ctx.writeAndFlush(new DefaultHttp2DataFrame(content, false)); + var endStream = frame.isEndStream(); + LOGGER.info(ctx.channel(), "headers received, sending response, end stream: {}", endStream); + ctx.writeAndFlush(new DefaultHttp2DataFrame(content, endStream)); } @Override From 9284d8738fa83973b57c0d660f7877e1bd161bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Sugawara=20=28=E2=88=A9=EF=BD=80-=C2=B4=29?= =?UTF-8?q?=E2=8A=83=E2=94=81=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E?= Date: Thu, 4 Dec 2025 18:11:44 -0800 Subject: [PATCH 08/60] Add more h2 tests including streaming --- .../it/RequestResponseHttp11ClearTest.java | 8 +- .../it/RequestResponseHttp11WithTlsTest.java | 14 ++- .../it/RequestResponseHttp2ClearTest.java | 9 +- .../it/RequestResponseHttp2WithAlpnTest.java | 95 ++++++++++++++++ .../it/RequestResponseHttp2WithTlsTest.java | 97 +++++++++++++++++ .../it/RequestStreamingHttp2ClearTest.java | 88 +++++++++++++++ .../it/RequestStreamingHttp2WithAlpnTest.java | 99 +++++++++++++++++ .../it/RequestStreamingHttp2WithTlsTest.java | 101 ++++++++++++++++++ .../smithy/java/http/client/it/TestUtils.java | 20 ++-- 9 files changed, 512 insertions(+), 19 deletions(-) create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithAlpnTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2ClearTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithAlpnTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java index f6d60dbf8..03799aca6 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.java.http.client.it; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -7,10 +12,10 @@ import java.time.Duration; import java.util.List; import java.util.Map; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; import software.amazon.smithy.java.http.client.HttpClient; import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; @@ -19,7 +24,6 @@ import software.amazon.smithy.java.http.client.it.server.NettyTestServer; import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp11ClientHandler; import software.amazon.smithy.java.http.client.it.server.TextResponseHttp11ClientHandler; -import software.amazon.smithy.java.http.api.HttpVersion; public class RequestResponseHttp11ClearTest { private static final String RESPONSE_CONTENTS = "Response sent from Http11ClearTest"; diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java index 584cb980e..f056b1e2f 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java @@ -1,11 +1,19 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.java.http.client.it; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.smithy.java.http.client.it.TestUtils.createClientSslContext; +import static software.amazon.smithy.java.http.client.it.TestUtils.createServerSslContextBuilder; + import java.net.InetAddress; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.List; import java.util.Map; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -21,10 +29,6 @@ import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; import software.amazon.smithy.java.http.client.it.server.TextResponseHttp11ClientHandler; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static software.amazon.smithy.java.http.client.it.TestUtils.createClientSslContext; -import static software.amazon.smithy.java.http.client.it.TestUtils.createServerSslContextBuilder; - public class RequestResponseHttp11WithTlsTest { private static final String RESPONSE_CONTENTS = "Response sent from Http2WithTls"; private static final String REQUEST_CONTENTS = "Request sent from Http2WithTls"; diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java index f5c94305b..ea40299f6 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.java.http.client.it; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -7,10 +12,10 @@ import java.time.Duration; import java.util.List; import java.util.Map; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; import software.amazon.smithy.java.http.client.HttpClient; import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; @@ -19,7 +24,6 @@ import software.amazon.smithy.java.http.client.it.server.NettyTestServer; import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp2ClientHandler; import software.amazon.smithy.java.http.client.it.server.TextResponseHttp2ClientHandler; -import software.amazon.smithy.java.http.api.HttpVersion; public class RequestResponseHttp2ClearTest { private static final String RESPONSE_CONTENTS = "Response sent from Http2ClearTest"; @@ -42,7 +46,6 @@ void setUp() throws Exception { .build(); server.start(); - DnsResolver staticDns = DnsResolver.staticMapping(Map.of( "localhost", List.of(InetAddress.getLoopbackAddress()))); diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithAlpnTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithAlpnTest.java new file mode 100644 index 000000000..3bd3a3dfb --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithAlpnTest.java @@ -0,0 +1,95 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.smithy.java.http.client.it.TestUtils.createServerSslContextBuilder; + +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; +import software.amazon.smithy.java.http.client.it.server.TextResponseHttp2ClientHandler; + +public class RequestResponseHttp2WithAlpnTest { + private static final String RESPONSE_CONTENTS = "Response sent from Http2WithTls"; + private static final String REQUEST_CONTENTS = "Request sent from Http2WithTls"; + private static TestCertificateGenerator.CertificateBundle bundle; + private NettyTestServer server; + private RequestCapturingHttp2ClientHandler requestCapturingHandler; + private HttpClient client; + + @BeforeAll + static void beforeAll() throws Exception { + bundle = TestCertificateGenerator.generateCertificates(); + } + + @BeforeEach + void setUp() throws Exception { + requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); + var multiplexer = new MultiplexingHttp2ClientHandler(requestCapturingHandler, + new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS)); + server = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.ALPN) + .http2HandlerFactory((ctx) -> multiplexer) + .sslContextBuilder(createServerSslContextBuilder(bundle)) + .build(); + server.start(); + + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + client = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .httpVersionPolicy(HttpVersionPolicy.AUTOMATIC) + .sslContext(TestUtils.createClientSslContext(bundle)) + .dnsResolver(staticDns) + .build()) + .build(); + } + + @AfterEach + void tearDown() { + server.stop(); + } + + @Test + public void canSendRequestAndReadResponse() throws Exception { + // -- Arrange + var request = TestUtils.plainTextHttp2Request("https://localhost:" + server.getPort(), REQUEST_CONTENTS); + + // -- Act + var response = client.send(request); + var bodyByteBuf = response.body().asByteBuffer(); + var bytes = new byte[bodyByteBuf.remaining()]; + bodyByteBuf.get(bytes); + var responseBody = new String(bytes, StandardCharsets.UTF_8); + client.close(); + + // -- Assert + var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); + assertEquals(REQUEST_CONTENTS, capturedRequestBody); + assertEquals(RESPONSE_CONTENTS, responseBody); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java new file mode 100644 index 000000000..77c6c5dc3 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java @@ -0,0 +1,97 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.smithy.java.http.client.it.TestUtils.createServerSslContextBuilder; + +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; +import software.amazon.smithy.java.http.client.it.server.TextResponseHttp2ClientHandler; + +@Disabled("The client doesn't support H2 with prior knowledge, let's find out if we should") +public class RequestResponseHttp2WithTlsTest { + private static final String RESPONSE_CONTENTS = "Response sent from Http2WithTls"; + private static final String REQUEST_CONTENTS = "Request sent from Http2WithTls"; + private static TestCertificateGenerator.CertificateBundle bundle; + private NettyTestServer server; + private RequestCapturingHttp2ClientHandler requestCapturingHandler; + private HttpClient client; + + @BeforeAll + static void beforeAll() throws Exception { + bundle = TestCertificateGenerator.generateCertificates(); + } + + @BeforeEach + void setUp() throws Exception { + requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); + var multiplexer = new MultiplexingHttp2ClientHandler(requestCapturingHandler, + new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS)); + server = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory((ctx) -> multiplexer) + .sslContextBuilder(createServerSslContextBuilder(bundle)) + .build(); + server.start(); + + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + client = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_2) + .sslContext(TestUtils.createClientSslContext(bundle)) + .dnsResolver(staticDns) + .build()) + .build(); + } + + @AfterEach + void tearDown() { + server.stop(); + } + + @Test + public void canSendRequestAndReadResponse() throws Exception { + // -- Arrange + var request = TestUtils.plainTextHttp2Request("https://localhost:" + server.getPort(), REQUEST_CONTENTS); + + // -- Act + var response = client.send(request); + var bodyByteBuf = response.body().asByteBuffer(); + var bytes = new byte[bodyByteBuf.remaining()]; + bodyByteBuf.get(bytes); + var responseBody = new String(bytes, StandardCharsets.UTF_8); + client.close(); + + // -- Assert + var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); + assertEquals(REQUEST_CONTENTS, capturedRequestBody); + assertEquals(RESPONSE_CONTENTS, responseBody); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2ClearTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2ClearTest.java new file mode 100644 index 000000000..5ab2e6baf --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2ClearTest.java @@ -0,0 +1,88 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.smithy.java.http.client.it.TestUtils.IPSUM_LOREM; +import static software.amazon.smithy.java.http.client.it.TestUtils.streamingBody; + +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.TextResponseHttp2ClientHandler; + +public class RequestStreamingHttp2ClearTest { + private static final String RESPONSE_CONTENTS = "Response sent from Http2ClearTest"; + private RequestCapturingHttp2ClientHandler requestCapturingHandler; + private NettyTestServer server; + private HttpClient client; + + @BeforeEach + void setUp() throws Exception { + requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); + var multiplexer = new MultiplexingHttp2ClientHandler(requestCapturingHandler, + new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS)); + server = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory((ctx) -> multiplexer) + .build(); + server.start(); + + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + client = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) + .dnsResolver(staticDns) + .build()) + .build(); + } + + @AfterEach + void tearDown() { + server.stop(); + } + + @Test + void canSendRequestAndReadResponse() throws Exception { + // -- Arrange + var request = TestUtils.request(HttpVersion.HTTP_2, + "http://localhost:" + server.getPort(), + streamingBody(IPSUM_LOREM)); + + // -- Act + var response = client.send(request); + requestCapturingHandler.streamCompleted().join(); + var bodyByteBuf = response.body().asByteBuffer(); + var bytes = new byte[bodyByteBuf.remaining()]; + bodyByteBuf.get(bytes); + var responseBody = new String(bytes, StandardCharsets.UTF_8); + client.close(); + + // -- Assert + var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); + assertEquals(String.join("", IPSUM_LOREM), capturedRequestBody); + assertEquals(RESPONSE_CONTENTS, responseBody); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithAlpnTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithAlpnTest.java new file mode 100644 index 000000000..b0e0f2b5f --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithAlpnTest.java @@ -0,0 +1,99 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.smithy.java.http.client.it.TestUtils.IPSUM_LOREM; +import static software.amazon.smithy.java.http.client.it.TestUtils.createServerSslContextBuilder; +import static software.amazon.smithy.java.http.client.it.TestUtils.streamingBody; + +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; +import software.amazon.smithy.java.http.client.it.server.TextResponseHttp2ClientHandler; + +public class RequestStreamingHttp2WithAlpnTest { + private static final String RESPONSE_CONTENTS = "Response sent from Http2ClearTest"; + private static TestCertificateGenerator.CertificateBundle bundle; + private RequestCapturingHttp2ClientHandler requestCapturingHandler; + private NettyTestServer server; + private HttpClient client; + + @BeforeAll + static void beforeAll() throws Exception { + bundle = TestCertificateGenerator.generateCertificates(); + } + + @BeforeEach + void setUp() throws Exception { + requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); + var multiplexer = new MultiplexingHttp2ClientHandler(requestCapturingHandler, + new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS)); + server = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.ALPN) + .http2HandlerFactory((ctx) -> multiplexer) + .sslContextBuilder(createServerSslContextBuilder(bundle)) + .build(); + server.start(); + + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + client = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .httpVersionPolicy(HttpVersionPolicy.AUTOMATIC) + .sslContext(TestUtils.createClientSslContext(bundle)) + .dnsResolver(staticDns) + .build()) + .build(); + } + + @AfterEach + void tearDown() { + server.stop(); + } + + @Test + void canSendRequestAndReadResponse() throws Exception { + // -- Arrange + var request = TestUtils.request(HttpVersion.HTTP_2, + "https://localhost:" + server.getPort(), + streamingBody(IPSUM_LOREM)); + + // -- Act + var response = client.send(request); + requestCapturingHandler.streamCompleted().join(); + var bodyByteBuf = response.body().asByteBuffer(); + var bytes = new byte[bodyByteBuf.remaining()]; + bodyByteBuf.get(bytes); + var responseBody = new String(bytes, StandardCharsets.UTF_8); + client.close(); + + // -- Assert + var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); + assertEquals(String.join("", IPSUM_LOREM), capturedRequestBody); + assertEquals(RESPONSE_CONTENTS, responseBody); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java new file mode 100644 index 000000000..9e175debf --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java @@ -0,0 +1,101 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.smithy.java.http.client.it.TestUtils.IPSUM_LOREM; +import static software.amazon.smithy.java.http.client.it.TestUtils.createServerSslContextBuilder; +import static software.amazon.smithy.java.http.client.it.TestUtils.streamingBody; + +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; +import software.amazon.smithy.java.http.client.it.server.TextResponseHttp2ClientHandler; + +@Disabled("The client doesn't support H2 with prior knowledge, let's find out if we should") +public class RequestStreamingHttp2WithTlsTest { + private static final String RESPONSE_CONTENTS = "Response sent from Http2ClearTest"; + private static TestCertificateGenerator.CertificateBundle bundle; + private RequestCapturingHttp2ClientHandler requestCapturingHandler; + private NettyTestServer server; + private HttpClient client; + + @BeforeAll + static void beforeAll() throws Exception { + bundle = TestCertificateGenerator.generateCertificates(); + } + + @BeforeEach + void setUp() throws Exception { + requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); + var multiplexer = new MultiplexingHttp2ClientHandler(requestCapturingHandler, + new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS)); + server = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory((ctx) -> multiplexer) + .sslContextBuilder(createServerSslContextBuilder(bundle)) + .build(); + server.start(); + + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + client = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_2) + .sslContext(TestUtils.createClientSslContext(bundle)) + .dnsResolver(staticDns) + .build()) + .build(); + } + + @AfterEach + void tearDown() { + server.stop(); + } + + @Test + void canSendRequestAndReadResponse() throws Exception { + // -- Arrange + var request = TestUtils.request(HttpVersion.HTTP_2, + "https://localhost:" + server.getPort(), + streamingBody(IPSUM_LOREM)); + + // -- Act + var response = client.send(request); + requestCapturingHandler.streamCompleted().join(); + var bodyByteBuf = response.body().asByteBuffer(); + var bytes = new byte[bodyByteBuf.remaining()]; + bodyByteBuf.get(bytes); + var responseBody = new String(bytes, StandardCharsets.UTF_8); + client.close(); + + // -- Assert + var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); + assertEquals(String.join("", IPSUM_LOREM), capturedRequestBody); + assertEquals(RESPONSE_CONTENTS, responseBody); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/TestUtils.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/TestUtils.java index 254c8ee0d..acbc7968c 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/TestUtils.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/TestUtils.java @@ -1,5 +1,11 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.java.http.client.it; +import io.netty.handler.ssl.SslContextBuilder; import java.net.URI; import java.net.URISyntaxException; import java.nio.ByteBuffer; @@ -11,22 +17,19 @@ import java.util.Iterator; import java.util.List; import java.util.concurrent.Flow; - -import io.netty.handler.ssl.SslContextBuilder; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; import software.amazon.smithy.java.http.api.HttpHeaders; import software.amazon.smithy.java.http.api.HttpRequest; import software.amazon.smithy.java.http.api.HttpVersion; import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; import software.amazon.smithy.java.io.datastream.DataStream; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; public class TestUtils { static final List IPSUM_LOREM = getIpsumLorem(); - private TestUtils() { - } + private TestUtils() {} public static HttpRequest plainTextHttp11Request(String uri, String contents) { return plainTextRequest(HttpVersion.HTTP_1_1, uri, contents); @@ -86,8 +89,7 @@ public static TrustManager[] createTrustManager(X509Certificate caCert) throws E // Initialize TrustManagerFactory with the KeyStore TrustManagerFactory tmf = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm() - ); + TrustManagerFactory.getDefaultAlgorithm()); tmf.init(keyStore); // Return the first TrustManager (typically there's only one) From b3bb0791bcf5df6a58b0edd6ab17f6202768eb44 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 4 Dec 2025 20:39:39 -0600 Subject: [PATCH 09/60] Fix subscribing to publisher data stream --- .../java/io/datastream/PublisherDataStream.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/io/src/main/java/software/amazon/smithy/java/io/datastream/PublisherDataStream.java b/io/src/main/java/software/amazon/smithy/java/io/datastream/PublisherDataStream.java index 92a34f670..0dd28a821 100644 --- a/io/src/main/java/software/amazon/smithy/java/io/datastream/PublisherDataStream.java +++ b/io/src/main/java/software/amazon/smithy/java/io/datastream/PublisherDataStream.java @@ -67,18 +67,21 @@ public InputStream asInputStream() { consumed = true; var subscriber = HttpResponse.BodySubscribers.ofInputStream(); - var delegate = new HttpBodySubscriberAdapter<>(subscriber); - subscribe(delegate); + innerSubscribe(new HttpBodySubscriberAdapter<>(subscriber)); return subscriber.getBody().toCompletableFuture().join(); } + private void innerSubscribe(Flow.Subscriber subscriber) { + consumed = true; + publisher.subscribe(subscriber); + } + @Override public void subscribe(Flow.Subscriber subscriber) { if (!isReplayable && consumed) { throw new IllegalStateException("DataStream is not replayable and has already been consumed"); } - consumed = true; - publisher.subscribe(subscriber); + innerSubscribe(subscriber); } } From 75dafe9347fe5fb96cc82c2acdff1322df242b06 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 4 Dec 2025 22:46:45 -0600 Subject: [PATCH 10/60] Add more tests, move integ tests to it/ --- .../smithy-java.module-conventions.gradle.kts | 3 - .../it/RequestResponseHttp11ClearTest.java | 0 .../it/RequestResponseHttp11WithTlsTest.java | 0 .../it/RequestResponseHttp2ClearTest.java | 0 .../it/RequestResponseHttp2WithAlpnTest.java | 0 .../it/RequestResponseHttp2WithTlsTest.java | 0 .../it/RequestStreamingHttp2ClearTest.java | 0 .../it/RequestStreamingHttp2WithAlpnTest.java | 0 .../it/RequestStreamingHttp2WithTlsTest.java | 0 .../smithy/java/http/client/it/TestUtils.java | 0 .../client/it/server/Http11ClientHandler.java | 0 .../it/server/Http11ClientHandlerFactory.java | 0 .../http/client/it/server/Http11Handler.java | 0 .../client/it/server/Http2ClientHandler.java | 0 .../it/server/Http2ClientHandlerFactory.java | 0 .../server/Http2ConnectionFrameHandler.java | 0 .../it/server/Http2StreamFrameHandler.java | 0 .../MultiplexingHttp11ClientHandler.java | 0 .../MultiplexingHttp2ClientHandler.java | 0 .../client/it/server/NettyTestLogger.java | 0 .../client/it/server/NettyTestServer.java | 0 .../RequestCapturingHttp11ClientHandler.java | 0 .../RequestCapturingHttp2ClientHandler.java | 0 .../client/it/server/ServerInitializer.java | 0 .../it/server/TestCertificateGenerator.java | 0 .../TextResponseHttp11ClientHandler.java | 0 .../TextResponseHttp2ClientHandler.java | 0 .../java/http/client/ManagedHttpExchange.java | 28 +- .../java/http/client/ProxySelector.java | 1 - .../client/connection/HttpSocketFactory.java | 25 +- .../java/http/client/dns/DnsResolver.java | 4 +- .../http/client/BufferedHttpExchangeTest.java | 114 +++++ .../java/http/client/HttpCredentialsTest.java | 57 +++ .../http/client/ManagedHttpExchangeTest.java | 431 ++++++++++++++++++ .../java/http/client/ProxySelectorTest.java | 91 ++++ .../java/http/client/RequestOptionsTest.java | 55 +++ .../http/client/connection/RouteTest.java | 138 ++++++ .../client/dns/StaticDnsResolverTest.java | 62 +++ .../client/dns/SystemDnsResolverTest.java | 38 ++ .../client/h1/FailingOutputStreamTest.java | 42 ++ .../java/http/client/h1/HttpUtilsTest.java | 113 +++++ 41 files changed, 1177 insertions(+), 25 deletions(-) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithAlpnTest.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2ClearTest.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithAlpnTest.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/TestUtils.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandler.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandlerFactory.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/Http11Handler.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandler.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandlerFactory.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/Http2StreamFrameHandler.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp11ClientHandler.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp2ClientHandler.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/NettyTestLogger.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/NettyTestServer.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp11ClientHandler.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp2ClientHandler.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/ServerInitializer.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/TestCertificateGenerator.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp11ClientHandler.java (100%) rename http/http-client/src/{test => it}/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java (100%) create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/BufferedHttpExchangeTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpCredentialsTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/ManagedHttpExchangeTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/ProxySelectorTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/RequestOptionsTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/RouteTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/dns/StaticDnsResolverTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/dns/SystemDnsResolverTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/FailingOutputStreamTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/HttpUtilsTest.java diff --git a/buildSrc/src/main/kotlin/smithy-java.module-conventions.gradle.kts b/buildSrc/src/main/kotlin/smithy-java.module-conventions.gradle.kts index 3a53f7f10..4832e8efc 100644 --- a/buildSrc/src/main/kotlin/smithy-java.module-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/smithy-java.module-conventions.gradle.kts @@ -72,6 +72,3 @@ tasks.jacocoTestReport { html.outputLocation.set(file("${layout.buildDirectory.get()}/reports/jacoco")) } } - -// Ensure integ tests are executed as part of test suite -tasks["test"].finalizedBy("integ") diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithAlpnTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithAlpnTest.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithAlpnTest.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithAlpnTest.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2ClearTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2ClearTest.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2ClearTest.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2ClearTest.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithAlpnTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithAlpnTest.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithAlpnTest.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithAlpnTest.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/TestUtils.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TestUtils.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/TestUtils.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TestUtils.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandler.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandler.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandlerFactory.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandlerFactory.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandlerFactory.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandlerFactory.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11Handler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11Handler.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http11Handler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11Handler.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandler.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandler.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandlerFactory.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandlerFactory.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandlerFactory.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandlerFactory.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2StreamFrameHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2StreamFrameHandler.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/Http2StreamFrameHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2StreamFrameHandler.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp11ClientHandler.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp11ClientHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp11ClientHandler.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp2ClientHandler.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp2ClientHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp2ClientHandler.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/NettyTestLogger.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/NettyTestLogger.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/NettyTestLogger.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/NettyTestLogger.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/NettyTestServer.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/NettyTestServer.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/NettyTestServer.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/NettyTestServer.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp11ClientHandler.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp11ClientHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp11ClientHandler.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp2ClientHandler.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp2ClientHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp2ClientHandler.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/ServerInitializer.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/ServerInitializer.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/ServerInitializer.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/ServerInitializer.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TestCertificateGenerator.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/TestCertificateGenerator.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TestCertificateGenerator.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/TestCertificateGenerator.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp11ClientHandler.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp11ClientHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp11ClientHandler.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java similarity index 100% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java index c017b818a..daed1bd74 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java @@ -68,6 +68,7 @@ public void write(int b) {} private boolean intercepted; private HttpResponse interceptedResponse; private InputStream responseIn; + private InputStream originalResponseBody; // body stream from delegate, needs draining on close ManagedHttpExchange( HttpExchange delegate, @@ -105,9 +106,18 @@ public InputStream responseBody() throws IOException { try { ensureIntercepted(); - InputStream body = interceptedResponse != null - ? interceptedResponse.body().asInputStream() - : delegate.responseBody(); + InputStream body; + if (interceptedResponse != null) { + // Interceptor replaced response - use replacement body + body = interceptedResponse.body().asInputStream(); + } else if (originalResponseBody != null) { + // Interceptors ran but didn't replace - use captured original + body = originalResponseBody; + originalResponseBody = null; // responseIn now owns it, will be drained via responseIn + } else { + // No interceptors - get body directly + body = delegate.responseBody(); + } // Wrap so closing the response body releases the connection to the pool responseIn = new DelegatedClosingInputStream(body, this::close); return responseIn; @@ -162,11 +172,16 @@ public void close() throws IOException { } closed = true; - // Drain response body before releasing connection (required for HTTP/1.1 connection reuse) + // Drain response bodies before releasing connection (required for HTTP/1.1 connection reuse). + // Must drain both the caller's stream (responseIn) and the original delegate body if different. + // This handles the case where an interceptor replaces the response but doesn't read the original. try { if (responseIn != null) { responseIn.transferTo(VERY_NULL_OUTPUT_STREAM); } + if (originalResponseBody != null && originalResponseBody != responseIn) { + originalResponseBody.transferTo(VERY_NULL_OUTPUT_STREAM); + } } catch (IOException ignored) { // Drain failed, so the connection cannot be reused safely errored = true; @@ -214,10 +229,13 @@ private void ensureIntercepted() throws IOException { return; } + // Capture original body stream - needs to be drained on close for connection reuse + originalResponseBody = delegate.responseBody(); + HttpResponse currentResponse = HttpResponse.builder() .statusCode(delegate.responseStatusCode()) .headers(delegate.responseHeaders()) - .body(DataStream.ofInputStream(delegate.responseBody())) + .body(DataStream.ofInputStream(originalResponseBody)) .build(); HttpResponse replacement; diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxySelector.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxySelector.java index 21c26cdc2..ddb04222b 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxySelector.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxySelector.java @@ -74,7 +74,6 @@ static ProxySelector direct() { * @return the ProxySelector that does not use failover. */ static ProxySelector noFailover(ProxySelector delegate) { - java.net.ProxySelector.getDefault(); return new ProxySelector() { @Override public List select(URI target, Context ctx) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java index 9dc20e5ba..35fc2bbee 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java @@ -5,10 +5,9 @@ package software.amazon.smithy.java.http.client.connection; -import java.io.UncheckedIOException; +import java.io.IOException; import java.net.InetAddress; import java.net.Socket; -import java.net.SocketException; import java.util.List; /** @@ -43,7 +42,7 @@ public interface HttpSocketFactory { * @param endpoints the resolved IP addresses for the route's host, in preference order * @return a new unconnected socket */ - Socket newSocket(Route route, List endpoints); + Socket newSocket(Route route, List endpoints) throws IOException; /** * Default factory used to create sockets. @@ -54,17 +53,13 @@ public interface HttpSocketFactory { * @param endpoints the resolved endpoints (unused in default implementation) * @return the created socket */ - static Socket defaultSocketFactory(Route route, List endpoints) { - try { - Socket socket = new Socket(); - socket.setTcpNoDelay(true); - socket.setKeepAlive(true); - // Larger buffers for high throughput - socket.setSendBufferSize(64 * 1024); - socket.setReceiveBufferSize(64 * 1024); - return socket; - } catch (SocketException e) { - throw new UncheckedIOException(e); - } + static Socket defaultSocketFactory(Route route, List endpoints) throws IOException { + Socket socket = new Socket(); + socket.setTcpNoDelay(true); + socket.setKeepAlive(true); + // Larger buffers for high throughput + socket.setSendBufferSize(64 * 1024); + socket.setReceiveBufferSize(64 * 1024); + return socket; } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/DnsResolver.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/DnsResolver.java index 391622064..d0b80c937 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/DnsResolver.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/dns/DnsResolver.java @@ -51,7 +51,9 @@ public interface DnsResolver { * * @param address the IP address that failed to connect */ - default void reportFailure(InetAddress address) {} + default void reportFailure(InetAddress address) { + // nothing by default + } /** * Purges cached entries for a specific hostname. diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/BufferedHttpExchangeTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/BufferedHttpExchangeTest.java new file mode 100644 index 000000000..63097055e --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/BufferedHttpExchangeTest.java @@ -0,0 +1,114 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; +import java.net.URI; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.io.datastream.DataStream; + +class BufferedHttpExchangeTest { + + @Test + void returnsRequest() { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com")) + .build(); + var response = HttpResponse.builder().statusCode(200).build(); + var exchange = new BufferedHttpExchange(request, response); + + assertEquals(request, exchange.request()); + } + + @Test + void returnsResponseStatusCode() { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com")) + .build(); + var response = HttpResponse.builder().statusCode(404).build(); + var exchange = new BufferedHttpExchange(request, response); + + assertEquals(404, exchange.responseStatusCode()); + } + + @Test + void returnsResponseHeaders() { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com")) + .build(); + var response = HttpResponse.builder() + .statusCode(200) + .withAddedHeader("Content-Type", "application/json") + .build(); + var exchange = new BufferedHttpExchange(request, response); + + assertEquals("application/json", exchange.responseHeaders().firstValue("Content-Type")); + } + + @Test + void returnsResponseBody() throws IOException { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com")) + .build(); + var response = HttpResponse.builder() + .statusCode(200) + .body(DataStream.ofString("hello")) + .build(); + var exchange = new BufferedHttpExchange(request, response); + var body = new String(exchange.responseBody().readAllBytes()); + + assertEquals("hello", body); + } + + @Test + void requestBodyIsNoOp() throws IOException { + var request = HttpRequest.builder() + .method("POST") + .uri(URI.create("http://example.com")) + .build(); + var response = HttpResponse.builder().statusCode(200).build(); + var exchange = new BufferedHttpExchange(request, response); + var out = exchange.requestBody(); + + assertNotNull(out); + out.write(new byte[] {1, 2, 3}); // should not throw + out.close(); + } + + @Test + void doesNotSupportBidirectionalStreaming() { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com")) + .build(); + var response = HttpResponse.builder().statusCode(200).build(); + var exchange = new BufferedHttpExchange(request, response); + + assertFalse(exchange.supportsBidirectionalStreaming()); + } + + @Test + void closeDoesNotThrow() throws IOException { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com")) + .build(); + var response = HttpResponse.builder().statusCode(200).build(); + var exchange = new BufferedHttpExchange(request, response); + + exchange.close(); // should not throw + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpCredentialsTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpCredentialsTest.java new file mode 100644 index 000000000..d8453c5a3 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpCredentialsTest.java @@ -0,0 +1,57 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; + +class HttpCredentialsTest { + + @Test + void basicAddsAuthHeader() { + var creds = new HttpCredentials.Basic("user", "pass"); + var request = HttpRequest.builder().method("GET").uri(URI.create("http://example.com")); + boolean result = creds.authenticate(request, null); + + assertTrue(result); + + var built = request.build(); + var authHeader = built.headers().firstValue("Authorization"); + var expected = "Basic " + Base64.getEncoder().encodeToString("user:pass".getBytes(StandardCharsets.UTF_8)); + assertEquals(expected, authHeader); + } + + @Test + void basicForProxyAddsProxyAuthHeader() { + var creds = new HttpCredentials.Basic("user", "pass", true); + var request = HttpRequest.builder().method("GET").uri(URI.create("http://example.com")); + boolean result = creds.authenticate(request, null); + + assertTrue(result); + var built = request.build(); + var authHeader = built.headers().firstValue("Proxy-Authorization"); + var expected = "Basic " + Base64.getEncoder().encodeToString("user:pass".getBytes(StandardCharsets.UTF_8)); + assertEquals(expected, authHeader); + } + + @Test + void basicReturnsFalseOnChallenge() { + var creds = new HttpCredentials.Basic("user", "pass"); + var request = HttpRequest.builder().method("GET").uri(URI.create("http://example.com")); + var priorResponse = HttpResponse.builder().statusCode(401).build(); + boolean result = creds.authenticate(request, priorResponse); + + assertFalse(result); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/ManagedHttpExchangeTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/ManagedHttpExchangeTest.java new file mode 100644 index 000000000..c075c5808 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/ManagedHttpExchangeTest.java @@ -0,0 +1,431 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import javax.net.ssl.SSLSession; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.context.Context; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.ConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpConnection; +import software.amazon.smithy.java.http.client.connection.Route; +import software.amazon.smithy.java.io.datastream.DataStream; + +class ManagedHttpExchangeTest { + + @Test + void releasesConnectionOnSuccessfulClose() throws IOException { + var released = new AtomicBoolean(false); + var pool = new TestConnectionPool() { + @Override + public void release(HttpConnection conn) { + released.set(true); + } + }; + var exchange = createExchange(pool, List.of()); + + exchange.responseBody().close(); + + assertTrue(released.get()); + } + + @Test + void evictsConnectionOnError() throws IOException { + var evicted = new AtomicBoolean(false); + var pool = new TestConnectionPool() { + @Override + public void evict(HttpConnection conn, boolean close) { + evicted.set(true); + } + }; + var delegate = new FailingHttpExchange(); + var exchange = createExchange(pool, List.of(), delegate); + + try { + exchange.responseStatusCode(); + } catch (IOException ignored) {} + + try { + exchange.close(); + } catch (IOException ignored) {} + + assertTrue(evicted.get()); + } + + @Test + void closingResponseBodyClosesExchange() throws IOException { + var closed = new AtomicBoolean(false); + var delegate = new TestHttpExchange() { + @Override + public void close() { + closed.set(true); + } + }; + var exchange = createExchange(new TestConnectionPool(), List.of(), delegate); + + exchange.responseBody().close(); + + assertTrue(closed.get()); + } + + @Test + void closeIsIdempotent() throws IOException { + var releaseCount = new AtomicInteger(0); + var pool = new TestConnectionPool() { + @Override + public void release(HttpConnection conn) { + releaseCount.incrementAndGet(); + } + }; + var exchange = createExchange(pool, List.of()); + + exchange.close(); + exchange.close(); + exchange.close(); + + assertEquals(1, releaseCount.get()); + } + + @Test + void interceptorCanReplaceResponse() throws IOException { + var interceptor = new HttpInterceptor() { + @Override + public HttpResponse interceptResponse( + HttpClient client, + HttpRequest request, + Context context, + HttpResponse response + ) { + return HttpResponse.builder() + .statusCode(999) + .body(DataStream.ofString("intercepted")) + .build(); + } + }; + var exchange = createExchange(new TestConnectionPool(), List.of(interceptor)); + + assertEquals(999, exchange.responseStatusCode()); + assertEquals("intercepted", new String(exchange.responseBody().readAllBytes())); + } + + @Test + void responseBodyReturnsSameStream() throws IOException { + var exchange = createExchange(new TestConnectionPool(), List.of()); + + var body1 = exchange.responseBody(); + var body2 = exchange.responseBody(); + + assertSame(body1, body2); + } + + @Test + void drainsResponseBodyWhenInterceptorReplacesWithoutReadingOriginal() throws IOException { + var drained = new AtomicBoolean(false); + var delegate = new TestHttpExchange() { + @Override + public InputStream responseBody() { + return new ByteArrayInputStream("original".getBytes()) { + @Override + public long transferTo(OutputStream out) throws IOException { + drained.set(true); + return super.transferTo(out); + } + }; + } + }; + + // Interceptor replaces response without reading original body + var interceptor = new HttpInterceptor() { + @Override + public HttpResponse interceptResponse( + HttpClient client, + HttpRequest request, + Context context, + HttpResponse response + ) { + // Replace without reading response.body() + return HttpResponse.builder() + .statusCode(999) + .body(DataStream.ofString("replaced")) + .build(); + } + }; + + var released = new AtomicBoolean(false); + var pool = new TestConnectionPool() { + @Override + public void release(HttpConnection conn) { + released.set(true); + } + }; + + var exchange = createExchange(pool, List.of(interceptor), delegate); + + // Access status (triggers interception) but don't call responseBody() + assertEquals(999, exchange.responseStatusCode()); + exchange.close(); + + assertTrue(drained.get(), "Original response body should be drained"); + assertTrue(released.get(), "Connection should be released"); + } + + @Test + void drainsOriginalBodyWhenInterceptorReplacesAndCallerReadsReplacement() throws IOException { + var originalDrained = new AtomicBoolean(false); + var delegate = new TestHttpExchange() { + @Override + public InputStream responseBody() { + return new ByteArrayInputStream("original".getBytes()) { + @Override + public long transferTo(OutputStream out) throws IOException { + originalDrained.set(true); + return super.transferTo(out); + } + }; + } + }; + + // Interceptor replaces response without reading original body + var interceptor = new HttpInterceptor() { + @Override + public HttpResponse interceptResponse( + HttpClient client, + HttpRequest request, + Context context, + HttpResponse response + ) { + return HttpResponse.builder() + .statusCode(999) + .body(DataStream.ofString("replaced")) + .build(); + } + }; + + var released = new AtomicBoolean(false); + var pool = new TestConnectionPool() { + @Override + public void release(HttpConnection conn) { + released.set(true); + } + }; + var exchange = createExchange(pool, List.of(interceptor), delegate); + + // Caller reads the replacement body + assertEquals("replaced", new String(exchange.responseBody().readAllBytes())); + exchange.close(); + + assertTrue(originalDrained.get(), + "Original response body should be drained even when caller reads replacement"); + assertTrue(released.get(), "Connection should be released"); + } + + @Test + void drainsResponseBodyWhenOnlyHeadersAccessedWithInterceptors() throws IOException { + var bodyDrained = new AtomicBoolean(false); + var delegate = new TestHttpExchange() { + @Override + public InputStream responseBody() { + return new ByteArrayInputStream("body".getBytes()) { + @Override + public long transferTo(OutputStream out) throws IOException { + bodyDrained.set(true); + return super.transferTo(out); + } + }; + } + }; + + // Pass-through interceptor that doesn't consume body + var interceptor = new HttpInterceptor() {}; + var released = new AtomicBoolean(false); + var pool = new TestConnectionPool() { + @Override + public void release(HttpConnection conn) { + released.set(true); + } + }; + + var exchange = createExchange(pool, List.of(interceptor), delegate); + + // Only access headers, never call responseBody() + exchange.responseHeaders(); + exchange.close(); + + assertTrue(bodyDrained.get(), "Response body should be drained even if not accessed"); + assertTrue(released.get(), "Connection should be released"); + } + + @Test + void doesNotDrainIfResponseNeverAccessed() throws IOException { + var bodyAccessed = new AtomicBoolean(false); + var delegate = new TestHttpExchange() { + @Override + public InputStream responseBody() { + bodyAccessed.set(true); + return super.responseBody(); + } + }; + + var released = new AtomicBoolean(false); + var pool = new TestConnectionPool() { + @Override + public void release(HttpConnection conn) { + released.set(true); + } + }; + + var exchange = createExchange(pool, List.of(), delegate); + + // Close without accessing response at all + exchange.close(); + + // Body should not be accessed since response was never read + assertFalse(bodyAccessed.get(), "Response body should not be accessed if response never read"); + assertTrue(released.get(), "Connection should still be released"); + } + + private ManagedHttpExchange createExchange(ConnectionPool pool, List interceptors) { + return createExchange(pool, interceptors, new TestHttpExchange()); + } + + private ManagedHttpExchange createExchange( + ConnectionPool pool, + List interceptors, + HttpExchange delegate + ) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com")) + .build(); + return new ManagedHttpExchange( + delegate, + new TestConnection(), + pool, + request, + Context.create(), + interceptors, + null); + } + + private static class TestHttpExchange implements HttpExchange { + @Override + public HttpRequest request() { + return null; + } + + @Override + public OutputStream requestBody() { + return OutputStream.nullOutputStream(); + } + + @Override + public InputStream responseBody() { + return new ByteArrayInputStream("test".getBytes()); + } + + @Override + public HttpHeaders responseHeaders() { + return HttpHeaders.of(Map.of()); + } + + @Override + public int responseStatusCode() throws IOException { + return 200; + } + + @Override + public HttpVersion responseVersion() { + return HttpVersion.HTTP_1_1; + } + + @Override + public void close() {} + } + + private static class FailingHttpExchange extends TestHttpExchange { + @Override + public int responseStatusCode() throws IOException { + throw new IOException("test error"); + } + } + + private static class TestConnection implements HttpConnection { + @Override + public HttpExchange newExchange(HttpRequest request) { + return null; + } + + @Override + public HttpVersion httpVersion() { + return HttpVersion.HTTP_1_1; + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public Route route() { + return Route.direct("http", "example.com", 80); + } + + @Override + public void close() {} + + @Override + public SSLSession sslSession() { + return null; + } + + @Override + public String negotiatedProtocol() { + return null; + } + + @Override + public boolean validateForReuse() { + return true; + } + } + + private static class TestConnectionPool implements ConnectionPool { + @Override + public HttpConnection acquire(Route route) { + return null; + } + + @Override + public void release(HttpConnection connection) {} + + @Override + public void evict(HttpConnection connection, boolean close) {} + + @Override + public void close() {} + + @Override + public void shutdown(Duration timeout) {} + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/ProxySelectorTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/ProxySelectorTest.java new file mode 100644 index 000000000..8a127203c --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/ProxySelectorTest.java @@ -0,0 +1,91 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.context.Context; + +class ProxySelectorTest { + + private static final URI TARGET = URI.create("https://example.com"); + private static final Context CTX = Context.create(); + + @Test + void directReturnsEmptyList() { + var selector = ProxySelector.direct(); + var result = selector.select(TARGET, CTX); + + assertTrue(result.isEmpty()); + } + + @Test + void ofReturnsSingleProxy() { + var proxy = new ProxyConfiguration(URI.create("http://proxy:8080"), ProxyConfiguration.ProxyType.HTTP); + var selector = ProxySelector.of(proxy); + var result = selector.select(TARGET, CTX); + + assertEquals(List.of(proxy), result); + } + + @Test + void ofReturnsMultipleProxiesInOrder() { + var proxy1 = new ProxyConfiguration(URI.create("http://proxy1:8080"), ProxyConfiguration.ProxyType.HTTP); + var proxy2 = new ProxyConfiguration(URI.create("http://proxy2:8080"), ProxyConfiguration.ProxyType.HTTP); + var selector = ProxySelector.of(proxy1, proxy2); + var result = selector.select(TARGET, CTX); + + assertEquals(List.of(proxy1, proxy2), result); + } + + @Test + void noFailoverReturnsOnlyFirstProxy() { + var proxy1 = new ProxyConfiguration(URI.create("http://proxy1:8080"), ProxyConfiguration.ProxyType.HTTP); + var proxy2 = new ProxyConfiguration(URI.create("http://proxy2:8080"), ProxyConfiguration.ProxyType.HTTP); + var delegate = ProxySelector.of(proxy1, proxy2); + var selector = ProxySelector.noFailover(delegate); + var result = selector.select(TARGET, CTX); + + assertEquals(List.of(proxy1), result); + } + + @Test + void noFailoverReturnsEmptyWhenDelegateReturnsEmpty() { + var delegate = ProxySelector.direct(); + var selector = ProxySelector.noFailover(delegate); + var result = selector.select(TARGET, CTX); + + assertTrue(result.isEmpty()); + } + + @Test + void noFailoverDelegatesConnectFailed() { + var failedProxy = new AtomicReference(); + var proxy = new ProxyConfiguration(URI.create("http://proxy:8080"), ProxyConfiguration.ProxyType.HTTP); + var delegate = new ProxySelector() { + @Override + public List select(URI target, Context context) { + return List.of(proxy); + } + + @Override + public void connectFailed(URI target, Context context, ProxyConfiguration p, IOException cause) { + failedProxy.set(p); + } + }; + + var selector = ProxySelector.noFailover(delegate); + selector.connectFailed(TARGET, CTX, proxy, new IOException("test")); + + assertEquals(proxy, failedProxy.get()); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/RequestOptionsTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/RequestOptionsTest.java new file mode 100644 index 000000000..db0fad394 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/RequestOptionsTest.java @@ -0,0 +1,55 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.context.Context; + +class RequestOptionsTest { + + @Test + void resolveInterceptorsReturnsClientOnlyWhenNoRequestInterceptors() { + var clientInterceptor = new NoOpInterceptor(); + var options = RequestOptions.defaults(); + var resolved = options.resolveInterceptors(List.of(clientInterceptor)); + + assertEquals(List.of(clientInterceptor), resolved); + } + + @Test + void resolveInterceptorsReturnsRequestOnlyWhenNoClientInterceptors() { + var requestInterceptor = new NoOpInterceptor(); + var options = RequestOptions.builder().addInterceptor(requestInterceptor).build(); + var resolved = options.resolveInterceptors(List.of()); + + assertEquals(List.of(requestInterceptor), resolved); + } + + @Test + void resolveInterceptorsCombinesClientThenRequest() { + var clientInterceptor = new NoOpInterceptor(); + var requestInterceptor = new NoOpInterceptor(); + var options = RequestOptions.builder().addInterceptor(requestInterceptor).build(); + var resolved = options.resolveInterceptors(List.of(clientInterceptor)); + + assertEquals(2, resolved.size()); + assertEquals(clientInterceptor, resolved.get(0)); + assertEquals(requestInterceptor, resolved.get(1)); + } + + @Test + void putContextAddsToContext() { + var key = Context.key("test"); + var options = RequestOptions.builder().putContext(key, "value").build(); + + assertEquals("value", options.context().get(key)); + } + + private static class NoOpInterceptor implements HttpInterceptor {} +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/RouteTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/RouteTest.java new file mode 100644 index 000000000..6562018fc --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/RouteTest.java @@ -0,0 +1,138 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.net.URI; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.client.ProxyConfiguration; + +class RouteTest { + + @Test + void fromUriUsesDefaultPortForHttp() { + var route = Route.from(URI.create("http://example.com/path")); + + assertEquals(80, route.port()); + } + + @Test + void fromUriUsesDefaultPortForHttps() { + var route = Route.from(URI.create("https://example.com/path")); + + assertEquals(443, route.port()); + } + + @Test + void fromUriUsesExplicitPort() { + var route = Route.from(URI.create("https://example.com:8443/path")); + + assertEquals(8443, route.port()); + } + + @Test + void fromUriNormalizesHostToLowercase() { + var route = Route.from(URI.create("https://EXAMPLE.COM/path")); + + assertEquals("example.com", route.host()); + } + + @Test + void fromUriIgnoresPathAndQuery() { + var route1 = Route.from(URI.create("https://example.com/users?id=1")); + var route2 = Route.from(URI.create("https://example.com/posts?id=2")); + + assertEquals(route1, route2); + } + + @Test + void fromUriThrowsOnMissingScheme() { + assertThrows(IllegalArgumentException.class, + () -> Route.from(URI.create("example.com/path"))); + } + + @Test + void fromUriThrowsOnMissingHost() { + assertThrows(IllegalArgumentException.class, + () -> Route.from(URI.create("http:///path"))); + } + + @Test + void constructorThrowsOnInvalidScheme() { + assertThrows(IllegalArgumentException.class, + () -> new Route("ftp", "example.com", 21, null)); + } + + @Test + void constructorThrowsOnInvalidPort() { + assertThrows(IllegalArgumentException.class, + () -> new Route("http", "example.com", 0, null)); + assertThrows(IllegalArgumentException.class, + () -> new Route("http", "example.com", 70000, null)); + } + + @Test + void isSecureReturnsTrueForHttps() { + var route = Route.direct("https", "example.com", 443); + + assertTrue(route.isSecure()); + } + + @Test + void isSecureReturnsFalseForHttp() { + var route = Route.direct("http", "example.com", 80); + + assertFalse(route.isSecure()); + } + + @Test + void connectionTargetReturnsProxyWhenProxied() { + var proxy = new ProxyConfiguration(URI.create("http://proxy:8080"), ProxyConfiguration.ProxyType.HTTP); + var route = Route.viaProxy("https", "example.com", 443, proxy); + + assertEquals("proxy:8080", route.connectionTarget()); + } + + @Test + void connectionTargetReturnsHostWhenDirect() { + var route = Route.direct("https", "example.com", 443); + + assertEquals("example.com:443", route.connectionTarget()); + } + + @Test + void tunnelTargetAlwaysReturnsTargetHost() { + var proxy = new ProxyConfiguration(URI.create("http://proxy:8080"), ProxyConfiguration.ProxyType.HTTP); + var route = Route.viaProxy("https", "example.com", 443, proxy); + + assertEquals("example.com:443", route.tunnelTarget()); + } + + @Test + void withProxyCreatesNewRouteWithProxy() { + var route = Route.direct("https", "example.com", 443); + var proxy = new ProxyConfiguration(URI.create("http://proxy:8080"), ProxyConfiguration.ProxyType.HTTP); + var proxied = route.withProxy(proxy); + + assertFalse(route.usesProxy()); + assertTrue(proxied.usesProxy()); + assertEquals(route.host(), proxied.host()); + } + + @Test + void withoutProxyCreatesNewRouteWithoutProxy() { + var proxy = new ProxyConfiguration(URI.create("http://proxy:8080"), ProxyConfiguration.ProxyType.HTTP); + var route = Route.viaProxy("https", "example.com", 443, proxy); + var direct = route.withoutProxy(); + + assertTrue(route.usesProxy()); + assertFalse(direct.usesProxy()); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/dns/StaticDnsResolverTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/dns/StaticDnsResolverTest.java new file mode 100644 index 000000000..e41a3dec3 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/dns/StaticDnsResolverTest.java @@ -0,0 +1,62 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.dns; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class StaticDnsResolverTest { + + @Test + void resolvesConfiguredHostname() throws Exception { + var addr = InetAddress.getLoopbackAddress(); + var resolver = new StaticDnsResolver(Map.of("example.com", List.of(addr))); + var result = resolver.resolve("example.com"); + + assertEquals(List.of(addr), result); + } + + @Test + void resolveIsCaseInsensitive() throws Exception { + var addr = InetAddress.getLoopbackAddress(); + var resolver = new StaticDnsResolver(Map.of("example.com", List.of(addr))); + + assertEquals(List.of(addr), resolver.resolve("EXAMPLE.COM")); + assertEquals(List.of(addr), resolver.resolve("Example.Com")); + } + + @Test + void throwsForUnknownHostname() { + var resolver = new StaticDnsResolver(Map.of("known.com", List.of(InetAddress.getLoopbackAddress()))); + + assertThrows(IOException.class, () -> resolver.resolve("unknown.com")); + } + + @Test + void returnsMultipleAddresses() throws Exception { + var addr1 = InetAddress.getByName("127.0.0.1"); + var addr2 = InetAddress.getByName("127.0.0.2"); + var resolver = new StaticDnsResolver(Map.of("example.com", List.of(addr1, addr2))); + var result = resolver.resolve("example.com"); + + assertEquals(2, result.size()); + assertEquals(addr1, result.get(0)); + assertEquals(addr2, result.get(1)); + } + + @Test + void ignoresEmptyMappings() { + var resolver = new StaticDnsResolver(Map.of("empty.com", List.of())); + + assertThrows(IOException.class, () -> resolver.resolve("empty.com")); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/dns/SystemDnsResolverTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/dns/SystemDnsResolverTest.java new file mode 100644 index 000000000..2604c86fa --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/dns/SystemDnsResolverTest.java @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.dns; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import org.junit.jupiter.api.Test; + +class SystemDnsResolverTest { + + @Test + void resolvesLocalhost() throws Exception { + var result = SystemDnsResolver.INSTANCE.resolve("localhost"); + + assertFalse(result.isEmpty()); + assertTrue(result.get(0).isLoopbackAddress()); + } + + @Test + void throwsForUnknownHost() { + assertThrows(IOException.class, + () -> SystemDnsResolver.INSTANCE.resolve("this.host.definitely.does.not.exist.invalid")); + } + + @Test + void resolvesIpAddressDirectly() throws Exception { + var result = SystemDnsResolver.INSTANCE.resolve("127.0.0.1"); + + assertFalse(result.isEmpty()); + assertTrue(result.get(0).isLoopbackAddress()); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/FailingOutputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/FailingOutputStreamTest.java new file mode 100644 index 000000000..91e40c75e --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/FailingOutputStreamTest.java @@ -0,0 +1,42 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import org.junit.jupiter.api.Test; + +class FailingOutputStreamTest { + + @Test + void writeThrowsConfiguredException() { + var expected = new IOException("test error"); + var stream = new FailingOutputStream(expected); + var thrown = assertThrows(IOException.class, () -> stream.write(1)); + + assertSame(expected, thrown); + } + + @Test + void flushThrowsConfiguredException() { + var expected = new IOException("test error"); + var stream = new FailingOutputStream(expected); + var thrown = assertThrows(IOException.class, stream::flush); + + assertSame(expected, thrown); + } + + @Test + void closeThrowsConfiguredException() { + var expected = new IOException("test error"); + var stream = new FailingOutputStream(expected); + var thrown = assertThrows(IOException.class, stream::close); + + assertSame(expected, thrown); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/HttpUtilsTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/HttpUtilsTest.java new file mode 100644 index 000000000..33c1bea6f --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/HttpUtilsTest.java @@ -0,0 +1,113 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpHeaders; + +class HttpUtilsTest { + + @Test + void internHeaderReturnsInternedStringForKnownHeaders() { + var buf = "content-type".getBytes(StandardCharsets.US_ASCII); + var result = HttpUtils.internHeader(buf, 0, buf.length); + + assertSame("content-type", result); + } + + @Test + void internHeaderIsCaseInsensitive() { + var buf = "Content-Type".getBytes(StandardCharsets.US_ASCII); + var result = HttpUtils.internHeader(buf, 0, buf.length); + + assertSame("content-type", result); + } + + @Test + void internHeaderReturnsNewStringForUnknownHeaders() { + var buf = "x-custom-header".getBytes(StandardCharsets.US_ASCII); + var result = HttpUtils.internHeader(buf, 0, buf.length); + + assertEquals("x-custom-header", result); + } + + @Test + void parseHeaderLineAddsHeader() { + var buf = "Content-Type: application/json".getBytes(StandardCharsets.US_ASCII); + var headers = HttpHeaders.ofModifiable(); + var name = HttpUtils.parseHeaderLine(buf, buf.length, headers); + + assertEquals("content-type", name); + assertEquals("application/json", headers.firstValue("content-type")); + } + + @Test + void parseHeaderLineTrimsWhitespace() { + var buf = "Content-Type: application/json ".getBytes(StandardCharsets.US_ASCII); + var headers = HttpHeaders.ofModifiable(); + HttpUtils.parseHeaderLine(buf, buf.length, headers); + + assertEquals("application/json", headers.firstValue("content-type")); + } + + @Test + void parseHeaderLineReturnsNullForMalformedLine() { + var buf = "no-colon-here".getBytes(StandardCharsets.US_ASCII); + var headers = HttpHeaders.ofModifiable(); + var name = HttpUtils.parseHeaderLine(buf, buf.length, headers); + + assertNull(name); + } + + @Test + void parseHeaderLineHandlesEmptyValue() { + var buf = "X-Empty:".getBytes(StandardCharsets.US_ASCII); + var headers = HttpHeaders.ofModifiable(); + HttpUtils.parseHeaderLine(buf, buf.length, headers); + + assertEquals("", headers.firstValue("X-Empty")); + } + + @Test + void parseHeaderLineReturnsNullForColonAtStart() { + var buf = ": value".getBytes(StandardCharsets.US_ASCII); + var headers = HttpHeaders.ofModifiable(); + var name = HttpUtils.parseHeaderLine(buf, buf.length, headers); + + assertNull(name); + } + + @Test + void parseHeaderLineHandlesValueWithColons() { + var buf = "Location: http://example.com:8080/path".getBytes(StandardCharsets.US_ASCII); + var headers = HttpHeaders.ofModifiable(); + HttpUtils.parseHeaderLine(buf, buf.length, headers); + + assertEquals("http://example.com:8080/path", headers.firstValue("location")); + } + + @Test + void parseHeaderLineHandlesTabWhitespace() { + var buf = "Content-Type:\t application/json".getBytes(StandardCharsets.US_ASCII); + var headers = HttpHeaders.ofModifiable(); + HttpUtils.parseHeaderLine(buf, buf.length, headers); + + assertEquals("application/json", headers.firstValue("content-type")); + } + + @Test + void internHeaderHandlesOffset() { + var buf = "XXXcontent-typeYYY".getBytes(StandardCharsets.US_ASCII); + var result = HttpUtils.internHeader(buf, 3, 12); + + assertSame("content-type", result); + } +} From b381fc10574e4e7ffc53964483ae138e79ba907a Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 4 Dec 2025 23:44:18 -0600 Subject: [PATCH 11/60] Fix HTTP/2 request body not sent - Fix H2Exchange to close H2DataOutputStream when request stream closes, ensuring DATA frame with END_STREAM is sent - Refactor DelegatedClosingInputStream/OutputStream to pass delegate to CloseCallback, enabling callers to close delegate when needed --- .../client/DelegatedClosingInputStream.java | 13 ++++++++----- .../client/DelegatedClosingOutputStream.java | 13 ++++++++----- .../java/http/client/ManagedHttpExchange.java | 2 +- .../smithy/java/http/client/h1/H1Exchange.java | 2 +- .../smithy/java/http/client/h2/H2Exchange.java | 7 +++++-- .../DelegatedClosingInputStreamTest.java | 18 +++++++++++++----- .../DelegatedClosingOutputStreamTest.java | 18 +++++++++++++----- 7 files changed, 49 insertions(+), 24 deletions(-) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java index 529dff7f1..84f0fe284 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.http.client; -import java.io.Closeable; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; @@ -17,18 +16,22 @@ *

The close callback is invoked at most once, and can be safely closed from any thread. */ public final class DelegatedClosingInputStream extends FilterInputStream { - private final Closeable onClose; + private final CloseCallback closeCallback; private final AtomicBoolean closed = new AtomicBoolean(false); - public DelegatedClosingInputStream(InputStream delegate, Closeable onClose) { + public DelegatedClosingInputStream(InputStream delegate, CloseCallback closeCallback) { super(delegate); - this.onClose = onClose; + this.closeCallback = closeCallback; } @Override public void close() throws IOException { if (closed.compareAndSet(false, true)) { - onClose.close(); + closeCallback.close(in); } } + + public interface CloseCallback { + void close(InputStream delegate) throws IOException; + } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java index 1283e9619..3728d2d73 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.http.client; -import java.io.Closeable; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -17,18 +16,22 @@ *

The close callback is invoked at most once, and can be safely closed from any thread. */ public final class DelegatedClosingOutputStream extends FilterOutputStream { - private final Closeable onClose; + private final CloseCallback closeCallback; private final AtomicBoolean closed = new AtomicBoolean(); - public DelegatedClosingOutputStream(OutputStream delegate, Closeable onClose) { + public DelegatedClosingOutputStream(OutputStream delegate, CloseCallback closeCallback) { super(delegate); - this.onClose = onClose; + this.closeCallback = closeCallback; } @Override public void close() throws IOException { if (closed.compareAndSet(false, true)) { - onClose.close(); + closeCallback.close(out); } } + + public interface CloseCallback { + void close(OutputStream delegate) throws IOException; + } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java index daed1bd74..c0298b83e 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java @@ -119,7 +119,7 @@ public InputStream responseBody() throws IOException { body = delegate.responseBody(); } // Wrap so closing the response body releases the connection to the pool - responseIn = new DelegatedClosingInputStream(body, this::close); + responseIn = new DelegatedClosingInputStream(body, in -> close()); return responseIn; } catch (IOException e) { errored = true; diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java index 5efbfc825..7192cd09f 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java @@ -151,7 +151,7 @@ public InputStream responseBody() throws IOException { parseStatusLineAndHeaders(); } // For HTTP/1.1, request is already complete, so close exchange when response closes - responseIn = new DelegatedClosingInputStream(createResponseStream(), this::close); + responseIn = new DelegatedClosingInputStream(createResponseStream(), in -> close()); } return responseIn; } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index f03d0ae3c..ab59a61f9 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -252,7 +252,10 @@ public OutputStream requestBody() { H2DataOutputStream rawOut = endStreamSent ? new H2DataOutputStream(this, 0) : new H2DataOutputStream(this, connection.getRemoteMaxFrameSize()); - requestOut = new DelegatedClosingOutputStream(rawOut, this::onRequestStreamClosed); + requestOut = new DelegatedClosingOutputStream(rawOut, rw -> { + rw.close(); // Send END_STREAM + onRequestStreamClosed(); + }); } return requestOut; } @@ -276,7 +279,7 @@ private void onRequestStreamClosed() throws IOException { } } - private void onResponseStreamClosed() throws IOException { + private void onResponseStreamClosed(InputStream _ignored) throws IOException { if (closedStreamCount.incrementAndGet() == 2) { close(); } diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStreamTest.java index 39af68181..95b244064 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStreamTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStreamTest.java @@ -6,31 +6,39 @@ package software.amazon.smithy.java.http.client; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; class DelegatedClosingInputStreamTest { @Test - void callsOnCloseCallback() throws IOException { + void callsCloseCallbackWithDelegate() throws IOException { var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); var closeCount = new AtomicInteger(0); + var passedDelegate = new AtomicReference(); - var stream = new DelegatedClosingInputStream(delegate, closeCount::incrementAndGet); + var stream = new DelegatedClosingInputStream(delegate, in -> { + passedDelegate.set(in); + closeCount.incrementAndGet(); + }); stream.close(); assertEquals(1, closeCount.get()); + assertSame(delegate, passedDelegate.get()); } @Test - void callsOnCloseOnlyOnce() throws IOException { + void callsCloseCallbackOnlyOnce() throws IOException { var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); var closeCount = new AtomicInteger(0); - var stream = new DelegatedClosingInputStream(delegate, closeCount::incrementAndGet); + var stream = new DelegatedClosingInputStream(delegate, in -> closeCount.incrementAndGet()); stream.close(); stream.close(); stream.close(); @@ -41,7 +49,7 @@ void callsOnCloseOnlyOnce() throws IOException { @Test void readsFromDelegate() throws IOException { var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); - var stream = new DelegatedClosingInputStream(delegate, () -> {}); + var stream = new DelegatedClosingInputStream(delegate, in -> {}); assertEquals(1, stream.read()); assertEquals(2, stream.read()); diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStreamTest.java index f161914df..a53feed2f 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStreamTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStreamTest.java @@ -7,31 +7,39 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; class DelegatedClosingOutputStreamTest { @Test - void callsOnCloseCallback() throws IOException { + void callsCloseCallbackWithDelegate() throws IOException { var delegate = new ByteArrayOutputStream(); var closeCount = new AtomicInteger(0); + var passedDelegate = new AtomicReference(); - var stream = new DelegatedClosingOutputStream(delegate, closeCount::incrementAndGet); + var stream = new DelegatedClosingOutputStream(delegate, out -> { + passedDelegate.set(out); + closeCount.incrementAndGet(); + }); stream.close(); assertEquals(1, closeCount.get()); + assertSame(delegate, passedDelegate.get()); } @Test - void callsOnCloseOnlyOnce() throws IOException { + void callsCloseCallbackOnlyOnce() throws IOException { var delegate = new ByteArrayOutputStream(); var closeCount = new AtomicInteger(0); - var stream = new DelegatedClosingOutputStream(delegate, closeCount::incrementAndGet); + var stream = new DelegatedClosingOutputStream(delegate, out -> closeCount.incrementAndGet()); stream.close(); stream.close(); stream.close(); @@ -42,7 +50,7 @@ void callsOnCloseOnlyOnce() throws IOException { @Test void writesToDelegate() throws IOException { var delegate = new ByteArrayOutputStream(); - var stream = new DelegatedClosingOutputStream(delegate, () -> {}); + var stream = new DelegatedClosingOutputStream(delegate, out -> {}); stream.write(new byte[] {1, 2, 3}); stream.flush(); From c6335d0a529b531f67c0a6faa070fd8c019e2b02 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 4 Dec 2025 23:50:09 -0600 Subject: [PATCH 12/60] Fix server alpn integ tests --- .../http/client/it/RequestResponseHttp2WithTlsTest.java | 2 -- .../http/client/it/RequestStreamingHttp2WithTlsTest.java | 2 -- .../amazon/smithy/java/http/client/it/TestUtils.java | 8 +++++++- .../amazon/smithy/java/http/client/h2/H2Exchange.java | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java index 77c6c5dc3..e4deeb116 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java @@ -16,7 +16,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import software.amazon.smithy.java.http.api.HttpVersion; import software.amazon.smithy.java.http.client.HttpClient; @@ -29,7 +28,6 @@ import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; import software.amazon.smithy.java.http.client.it.server.TextResponseHttp2ClientHandler; -@Disabled("The client doesn't support H2 with prior knowledge, let's find out if we should") public class RequestResponseHttp2WithTlsTest { private static final String RESPONSE_CONTENTS = "Response sent from Http2WithTls"; private static final String REQUEST_CONTENTS = "Request sent from Http2WithTls"; diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java index 9e175debf..29f50c133 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java @@ -18,7 +18,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import software.amazon.smithy.java.http.api.HttpVersion; import software.amazon.smithy.java.http.client.HttpClient; @@ -31,7 +30,6 @@ import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; import software.amazon.smithy.java.http.client.it.server.TextResponseHttp2ClientHandler; -@Disabled("The client doesn't support H2 with prior knowledge, let's find out if we should") public class RequestStreamingHttp2WithTlsTest { private static final String RESPONSE_CONTENTS = "Response sent from Http2ClearTest"; private static TestCertificateGenerator.CertificateBundle bundle; diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TestUtils.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TestUtils.java index acbc7968c..fb93d87bc 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TestUtils.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TestUtils.java @@ -5,6 +5,7 @@ package software.amazon.smithy.java.http.client.it; +import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.SslContextBuilder; import java.net.URI; import java.net.URISyntaxException; @@ -70,7 +71,12 @@ public static SslContextBuilder createServerSslContextBuilder( TestCertificateGenerator.CertificateBundle bundle ) throws Exception { return SslContextBuilder - .forServer(bundle.serverPrivateKey, bundle.serverCertificate); + .forServer(bundle.serverPrivateKey, bundle.serverCertificate) + .applicationProtocolConfig(new io.netty.handler.ssl.ApplicationProtocolConfig( + ApplicationProtocolConfig.Protocol.ALPN, + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + "h2", "http/1.1")); } public static SSLContext createClientSslContext( diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index ab59a61f9..e0257b473 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -253,7 +253,7 @@ public OutputStream requestBody() { ? new H2DataOutputStream(this, 0) : new H2DataOutputStream(this, connection.getRemoteMaxFrameSize()); requestOut = new DelegatedClosingOutputStream(rawOut, rw -> { - rw.close(); // Send END_STREAM + rw.close(); // Send END_STREAM onRequestStreamClosed(); }); } From 8cd32703ce4231d7e6b74bf3c75ee90002ae0fc5 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Fri, 5 Dec 2025 11:41:54 -0600 Subject: [PATCH 13/60] Add h1 tests, docs, fix proxy and chunked bugs --- .../smithy/java/http/client/it/TestUtils.java | 3 +- .../smithy/java/http/client/HttpExchange.java | 70 +++- .../java/http/client/ManagedHttpExchange.java | 5 + .../http/client/h1/ChunkedInputStream.java | 16 +- .../http/client/h1/ChunkedOutputStream.java | 45 ++- .../java/http/client/h1/H1Exchange.java | 24 +- .../java/http/client/h1/ProxyTunnel.java | 6 +- .../java/http/client/h2/H2Connection.java | 31 ++ .../java/http/client/h2/H2Exchange.java | 27 +- .../java/http/client/h2/H2StreamWriter.java | 50 +++ .../client/h1/ChunkedInputStreamTest.java | 319 ++++++++++++++++++ .../client/h1/ChunkedOutputStreamTest.java | 157 +++++++++ .../java/http/client/h1/HttpUtilsTest.java | 132 ++++---- .../java/http/client/h1/ProxyTunnelTest.java | 181 ++++++++++ 14 files changed, 970 insertions(+), 96 deletions(-) create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStreamTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStreamTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ProxyTunnelTest.java diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TestUtils.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TestUtils.java index fb93d87bc..013f1ee75 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TestUtils.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TestUtils.java @@ -76,7 +76,8 @@ public static SslContextBuilder createServerSslContextBuilder( ApplicationProtocolConfig.Protocol.ALPN, ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, - "h2", "http/1.1")); + "h2", + "http/1.1")); } public static SSLContext createClientSslContext( diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java index d70300105..726f0b716 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java @@ -81,10 +81,35 @@ static HttpExchange newBufferedExchange(HttpRequest request, HttpResponse respon *

Closing this stream signals the end of the request body. For HTTP/2, closing this stream while the response * stream is also closed will automatically close the exchange. * + * {@snippet : + * try (OutputStream out = exchange.requestBody()) { + * exchange.request().body().asInputStream().transferTo(out); + * } + * } + * * @return request body stream + * @see #writeRequestBody() */ OutputStream requestBody(); + /** + * Write the request body from {@link HttpRequest#body()} to the output stream. + * + *

This is a convenience method equivalent to: + * {@snippet : + * try (OutputStream out = exchange.requestBody()) { + * exchange.request().body().asInputStream().transferTo(out); + * } + * } + * + * @throws IOException if an I/O error occurs + */ + default void writeRequestBody() throws IOException { + try (OutputStream out = requestBody()) { + request().body().asInputStream().transferTo(out); + } + } + /** * HTTP version from response. Blocks until received. * @@ -114,6 +139,12 @@ static HttpExchange newBufferedExchange(HttpRequest request, HttpResponse respon *

Closing this stream will automatically close the exchange for HTTP/1.1. For HTTP/2, closing this stream * while the request stream is also closed will automatically close the exchange. * + * {@snippet : + * try (InputStream in = exchange.responseBody()) { + * byte[] body = in.readAllBytes(); + * } + * } + * * @return the response input stream to read. */ InputStream responseBody() throws IOException; @@ -137,8 +168,18 @@ static HttpExchange newBufferedExchange(HttpRequest request, HttpResponse respon *

  • HTTP/2: Via HEADERS frame after DATA with END_STREAM (RFC 9113 Section 8.1)
  • * * - *

    IMPORTANT: Trailers are only available after the entire response body - * has been read. Calling this before the body is fully consumed returns null. + *

    IMPORTANT: Trailers are only available after the entire response body has been read. + * Calling this before the body is fully consumed returns null. + * + * {@snippet : + * try (InputStream in = exchange.responseBody()) { + * in.readAllBytes(); // must fully consume body first + * } + * HttpHeaders trailers = exchange.responseTrailerHeaders(); + * if (trailers != null) { + * String checksum = trailers.firstValue("checksum").orElse(null); + * } + * } * * @return trailer headers, or null if no trailers were received */ @@ -159,6 +200,31 @@ default boolean supportsBidirectionalStreaming() { return false; } + /** + * Set trailer headers to be sent after the request body. + * + *

    Must be called before closing the request body stream. Trailers are supported in: + *

      + *
    • HTTP/1.1: Only with chunked transfer encoding
    • + *
    • HTTP/2: Always supported
    • + *
    + * + *

    Example usage: + * {@snippet : + * HttpExchange exchange = connection.newExchange(request); + * try (OutputStream body = exchange.requestBody()) { + * body.write(data); + * exchange.setRequestTrailers(HttpHeaders.of(Map.of("checksum", List.of("abc123")))); + * } // trailers sent on close + * } + * + * @param trailers the trailer headers to send + * @throws IllegalStateException if trailers are not supported (e.g., H1 without chunked encoding) + */ + default void setRequestTrailers(HttpHeaders trailers) { + throw new UnsupportedOperationException("Request trailers not supported"); + } + /** * {@inheritDoc} * diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java index c0298b83e..b151335e5 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java @@ -165,6 +165,11 @@ public boolean supportsBidirectionalStreaming() { return delegate.supportsBidirectionalStreaming(); } + @Override + public void setRequestTrailers(HttpHeaders trailers) { + delegate.setRequestTrailers(trailers); + } + @Override public void close() throws IOException { if (closed) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java index 008587f18..20aa06a61 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import software.amazon.smithy.java.http.api.HttpHeaders; import software.amazon.smithy.java.http.api.ModifiableHttpHeaders; @@ -68,7 +69,6 @@ public int read() throws IOException { if (b != -1) { chunkRemaining--; } else { - // Unexpected EOF throw new IOException("Unexpected end of stream in chunked encoding"); } @@ -79,8 +79,6 @@ public int read() throws IOException { public int read(byte[] b, int off, int len) throws IOException { if (closed || eof) { return -1; - } else if (b == null) { - throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { @@ -148,17 +146,15 @@ public void close() throws IOException { if (closed) { return; } - closed = true; - // Drain remaining chunks to allow connection reuse + // Drain remaining chunks to allow connection reuse (before setting closed flag) if (!eof) { - byte[] drain = new byte[8192]; - while (read(drain) != -1) { - // Discard - } + // use transferTo from delegate since it's optimized to not allocate + transferTo(OutputStream.nullOutputStream()); } - // Don't close delegate - connection may be reused + closed = true; + // Note: we don't close the delegate since the connection may be reused } /** diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStream.java index e19b3c0dd..5c2d67068 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStream.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import software.amazon.smithy.java.http.api.HttpHeaders; /** * OutputStream that writes HTTP/1.1 chunked transfer encoding format (RFC 7230 Section 4.1). @@ -20,6 +21,7 @@ final class ChunkedOutputStream extends OutputStream { private final byte[] buffer; private int bufferPos = 0; private boolean closed = false; + private HttpHeaders trailers; // Default chunk size: 8KB private static final int DEFAULT_CHUNK_SIZE = 8192; @@ -40,8 +42,7 @@ final class ChunkedOutputStream extends OutputStream { ChunkedOutputStream(OutputStream delegate, int chunkSize) { if (delegate == null) { throw new NullPointerException("delegate"); - } - if (chunkSize <= 0) { + } else if (chunkSize <= 0) { throw new IllegalArgumentException("chunkSize must be positive: " + chunkSize); } @@ -49,6 +50,17 @@ final class ChunkedOutputStream extends OutputStream { this.buffer = new byte[chunkSize]; } + /** + * Set trailer headers to be sent after the final chunk. + * + *

    Must be called before {@link #close()}. + * + * @param trailers the trailer headers to send + */ + void setTrailers(HttpHeaders trailers) { + this.trailers = trailers; + } + @Override public void write(int b) throws IOException { if (closed) { @@ -71,15 +83,9 @@ public void write(byte[] b) throws IOException { public void write(byte[] b, int off, int len) throws IOException { if (closed) { throw new IOException("Stream closed"); - } - - if (b == null) { - throw new NullPointerException(); - } - if (off < 0 || len < 0 || len > b.length - off) { + } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); - } - if (len == 0) { + } else if (len == 0) { return; } @@ -170,17 +176,32 @@ private void writeChunk(byte[] data, int off, int len) throws IOException { } /** - * Write the final 0-sized chunk. + * Write the final 0-sized chunk with optional trailers. * *

    Format: * 0\r\n + * [trailer-name: trailer-value\r\n]* * \r\n */ private void writeFinalChunk() throws IOException { - // Write "0\r\n\r\n" delegate.write('0'); delegate.write('\r'); delegate.write('\n'); + + if (trailers != null) { + for (var entry : trailers) { + String name = entry.getKey(); + for (String value : entry.getValue()) { + delegate.write(name.getBytes(StandardCharsets.US_ASCII)); + delegate.write(':'); + delegate.write(' '); + delegate.write(value.getBytes(StandardCharsets.US_ASCII)); + delegate.write('\r'); + delegate.write('\n'); + } + } + } + delegate.write('\r'); delegate.write('\n'); } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java index 7192cd09f..3e2c36d02 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java @@ -157,8 +157,11 @@ public InputStream responseBody() throws IOException { } @Override - public boolean supportsBidirectionalStreaming() { - return false; + public void setRequestTrailers(HttpHeaders trailers) { + if (!(requestOut instanceof ChunkedOutputStream cos)) { + throw new IllegalStateException("Request trailers require chunked transfer encoding"); + } + cos.setTrailers(trailers); } @Override @@ -298,7 +301,15 @@ private void writeRequestLine(UnsyncBufferedOutputStream out) throws IOException out.write(' '); URI uri = request.uri(); - if (isHttpProxyWithoutTunnel()) { + if ("CONNECT".equals(request.method())) { + // CONNECT uses authority-form: host:port + out.writeAscii(uri.getHost()); + int port = uri.getPort(); + if (port != -1) { + out.write(':'); + out.writeAscii(Integer.toString(port)); + } + } else if (isHttpProxyWithoutTunnel()) { out.writeAscii(uri.toString()); } else { String path = uri.getRawPath(); @@ -557,12 +568,9 @@ private static boolean noBodyResponseStatus(int statusCode) { } /** - * Get default port for scheme (80 for http, 443 for https). + * Get default port for scheme (80 for http or unknown, 443 for https). */ private static int defaultPort(String scheme) { - if ("https".equalsIgnoreCase(scheme)) { - return 443; - } - return 80; // http or unknown + return "https".equalsIgnoreCase(scheme) ? 443 : 80; } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ProxyTunnel.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ProxyTunnel.java index 704e3bd40..a6f1f8d2c 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ProxyTunnel.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ProxyTunnel.java @@ -62,10 +62,12 @@ public static Result establish( HttpResponse priorResponse = null; do { + // CONNECT uses authority-form request-target (host:port) + String authority = targetHost + ":" + targetPort; HttpRequest.Builder requestBuilder = HttpRequest.builder() .method("CONNECT") - .uri(URI.create("http://" + targetHost + ":" + targetPort)) - .withAddedHeader("Host", targetHost + ":" + targetPort) + .uri(URI.create("http://" + authority)) + .withAddedHeader("Host", authority) .withAddedHeader("Proxy-Connection", "Keep-Alive"); if (credentials != null) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java index 0ca60cf46..66dcb9ef1 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java @@ -46,6 +46,7 @@ import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; +import software.amazon.smithy.java.http.api.HttpHeaders; import software.amazon.smithy.java.http.api.HttpRequest; import software.amazon.smithy.java.http.api.HttpVersion; import software.amazon.smithy.java.http.client.HttpExchange; @@ -686,6 +687,36 @@ void queueData(int streamId, byte[] data, int offset, int length, int flags) thr } } + /** + * Queue trailer HEADERS frame for writing via the encoder/writer thread. + * + *

    Trailers are sent as a HEADERS frame with END_STREAM flag after all DATA frames. + * Unlike request headers, trailers use an existing stream ID and must not contain + * pseudo-headers. + * + * @param streamId the stream ID + * @param trailers the trailer headers to send + * @throws IOException if the write fails + */ + void queueTrailers(int streamId, HttpHeaders trailers) throws IOException { + CompletableFuture completion = new CompletableFuture<>(); + if (!streamWriter.submitWork( + new H2StreamWriter.WorkItem.WriteTrailers(streamId, trailers, completion), + writeTimeout.toMillis())) { + throw new IOException("Write queue full - connection overloaded"); + } + + try { + completion.join(); + } catch (Exception e) { + Throwable cause = e.getCause(); + if (cause instanceof IOException ioe) { + throw ioe; + } + throw new IOException("Failed to write trailers", cause != null ? cause : e); + } + } + /** * Get the current number of active streams. */ diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index e0257b473..753566984 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -111,6 +111,7 @@ enum StreamState { // Request state private volatile boolean endStreamSent = false; private volatile OutputStream requestOut; + private volatile HttpHeaders requestTrailers; // Response body input stream private volatile InputStream responseIn; @@ -311,6 +312,11 @@ public boolean supportsBidirectionalStreaming() { return true; } + @Override + public void setRequestTrailers(HttpHeaders trailers) { + this.requestTrailers = trailers; + } + @Override public void close() { if (!closed.compareAndSet(false, true)) { @@ -608,6 +614,10 @@ void updateStreamSendWindow(int increment) throws H2Exception { * @throws SocketTimeoutException if write timeout expires waiting for flow control window */ void writeData(byte[] data, int offset, int length, boolean endStream) throws IOException { + // If trailers are set and this is the last data, don't set END_STREAM on DATA frame + // - trailers will carry END_STREAM instead + boolean hasTrailers = requestTrailers != null; + while (length > 0) { int toSend; @@ -635,8 +645,9 @@ void writeData(byte[] data, int offset, int length, boolean endStream) throws IO flowControlLock.unlock(); } - boolean isLast = endStream && (toSend == length); - int flags = isLast ? FLAG_END_STREAM : 0; + boolean isLastChunk = (toSend == length); + // Only set END_STREAM on DATA if this is the last chunk AND no trailers + int flags = (endStream && isLastChunk && !hasTrailers) ? FLAG_END_STREAM : 0; // Queue the write - writer thread handles I/O with batching connection.queueData(streamId, data, offset, toSend, flags); @@ -647,6 +658,10 @@ void writeData(byte[] data, int offset, int length, boolean endStream) throws IO } if (endStream) { + if (hasTrailers) { + // Send trailers with END_STREAM + connection.queueTrailers(streamId, requestTrailers); + } endStreamSent = true; // Update stream state if (streamState == StreamState.OPEN) { @@ -658,11 +673,15 @@ void writeData(byte[] data, int offset, int length, boolean endStream) throws IO } /** - * Send END_STREAM without data. + * Send END_STREAM without data, or send trailers if set. */ void sendEndStream() throws IOException { if (!endStreamSent) { - connection.queueData(streamId, EMPTY_DATA, 0, 0, FLAG_END_STREAM); + if (requestTrailers != null) { + connection.queueTrailers(streamId, requestTrailers); + } else { + connection.queueData(streamId, EMPTY_DATA, 0, 0, FLAG_END_STREAM); + } endStreamSent = true; // Update stream state if (streamState == StreamState.OPEN) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamWriter.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamWriter.java index 3d0176fbf..d69da459b 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamWriter.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamWriter.java @@ -23,6 +23,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import software.amazon.smithy.java.http.api.HttpHeaders; import software.amazon.smithy.java.http.api.HttpRequest; import software.amazon.smithy.java.http.client.h2.hpack.HpackEncoder; import software.amazon.smithy.java.io.ByteBufferOutputStream; @@ -71,6 +72,12 @@ record WriteData( int flags, CompletableFuture completion) implements WorkItem {} + /** Encode and write trailer HEADERS frame with END_STREAM. */ + record WriteTrailers( + int streamId, + HttpHeaders trailers, + CompletableFuture completion) implements WorkItem {} + /** Write a RST_STREAM frame. */ record WriteRst( int streamId, @@ -278,6 +285,7 @@ private void processItem(WorkItem item) throws IOException { case WorkItem.EncodeHeaders h -> processEncodeHeaders(h); case WorkItem.WriteData d -> frameCodec.writeFrame(FRAME_TYPE_DATA, d.flags(), d.streamId(), d.data(), d.offset(), d.length()); + case WorkItem.WriteTrailers t -> processWriteTrailers(t); case WorkItem.WriteRst r -> frameCodec.writeRstStream(r.streamId(), r.errorCode()); case WorkItem.WriteGoaway g -> @@ -294,6 +302,47 @@ private void processItem(WorkItem item) throws IOException { } } + /** + * Process trailer headers: encode via HPACK and write HEADERS frame with END_STREAM. + * + *

    Unlike request headers, trailers: + *

      + *
    • Use an existing stream ID (no allocation)
    • + *
    • Must NOT contain pseudo-headers
    • + *
    • Always have END_STREAM flag set
    • + *
    + */ + private void processWriteTrailers(WorkItem.WriteTrailers req) throws IOException { + byte[] headerBlock = encodeTrailers(req.trailers()); + frameCodec.writeHeaders(req.streamId(), headerBlock, 0, headerBlock.length, true); + } + + /** + * Encode trailer headers using HPACK. + * + *

    Trailers must not contain pseudo-headers (names starting with ':'). + */ + private byte[] encodeTrailers(HttpHeaders trailers) throws IOException { + headerEncodeBuffer.reset(); + hpackEncoder.beginHeaderBlock(headerEncodeBuffer); + + for (var entry : trailers) { + String name = entry.getKey(); + if (name.startsWith(":")) { + throw new IOException("Trailers must not contain pseudo-header: " + name); + } + boolean sensitive = SENSITIVE_HEADERS.contains(name); + for (String value : entry.getValue()) { + hpackEncoder.encodeHeader(headerEncodeBuffer, name, value, sensitive); + } + } + + ByteBuffer buffer = headerEncodeBuffer.toByteBuffer(); + byte[] result = new byte[buffer.remaining()]; + buffer.get(result); + return result; + } + /** * Process an encode headers request: allocate stream, encode HPACK, write frame. */ @@ -369,6 +418,7 @@ private void completeItem(WorkItem item, IOException error) { CompletableFuture completion = switch (item) { case WorkItem.EncodeHeaders h -> h.writeComplete(); case WorkItem.WriteData d -> d.completion(); + case WorkItem.WriteTrailers t -> t.completion(); case WorkItem.WriteRst r -> r.completion(); case WorkItem.WriteGoaway g -> null; case WorkItem.WriteWindowUpdate w -> null; diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStreamTest.java new file mode 100644 index 000000000..82c335566 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStreamTest.java @@ -0,0 +1,319 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.client.UnsyncBufferedInputStream; + +class ChunkedInputStreamTest { + + private ChunkedInputStream chunked(String data) { + var bytes = data.getBytes(StandardCharsets.US_ASCII); + return new ChunkedInputStream(new UnsyncBufferedInputStream(new ByteArrayInputStream(bytes), 256)); + } + + @Test + void readsSingleChunk() throws IOException { + var stream = chunked("5\r\nhello\r\n0\r\n\r\n"); + + byte[] result = stream.readAllBytes(); + + assertArrayEquals("hello".getBytes(), result); + } + + @Test + void readsMultipleChunks() throws IOException { + var stream = chunked("5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n"); + + byte[] result = stream.readAllBytes(); + + assertArrayEquals("hello world".getBytes(), result); + } + + @Test + void readsEmptyBody() throws IOException { + var stream = chunked("0\r\n\r\n"); + + byte[] result = stream.readAllBytes(); + + assertEquals(0, result.length); + } + + @Test + void readsSingleByte() throws IOException { + var stream = chunked("3\r\nabc\r\n0\r\n\r\n"); + + assertEquals('a', stream.read()); + assertEquals('b', stream.read()); + assertEquals('c', stream.read()); + assertEquals(-1, stream.read()); + } + + @Test + void readsUppercaseHex() throws IOException { + var stream = chunked("A\r\n0123456789\r\n0\r\n\r\n"); + + byte[] result = stream.readAllBytes(); + + assertEquals(10, result.length); + } + + @Test + void readsLowercaseHex() throws IOException { + var stream = chunked("a\r\n0123456789\r\n0\r\n\r\n"); + + byte[] result = stream.readAllBytes(); + + assertEquals(10, result.length); + } + + @Test + void ignoresChunkExtensions() throws IOException { + var stream = chunked("5;name=value\r\nhello\r\n0\r\n\r\n"); + + byte[] result = stream.readAllBytes(); + + assertArrayEquals("hello".getBytes(), result); + } + + @Test + void parsesTrailers() throws IOException { + var stream = chunked("5\r\nhello\r\n0\r\nX-Checksum: abc123\r\n\r\n"); + stream.readAllBytes(); + + var trailers = stream.getTrailers(); + + assertEquals("abc123", trailers.firstValue("x-checksum")); + } + + @Test + void parsesMultipleTrailers() throws IOException { + var stream = chunked("5\r\nhello\r\n0\r\nX-Foo: bar\r\nX-Baz: qux\r\n\r\n"); + stream.readAllBytes(); + + var trailers = stream.getTrailers(); + + assertEquals("bar", trailers.firstValue("x-foo")); + assertEquals("qux", trailers.firstValue("x-baz")); + } + + @Test + void trailersNullBeforeEof() throws IOException { + var stream = chunked("5\r\nhello\r\n0\r\nX-Foo: bar\r\n\r\n"); + + assertNull(stream.getTrailers()); + } + + @Test + void trailersNullWhenNonePresent() throws IOException { + var stream = chunked("5\r\nhello\r\n0\r\n\r\n"); + stream.readAllBytes(); + + assertNull(stream.getTrailers()); + } + + @Test + void availableReturnsChunkRemaining() throws IOException { + var stream = chunked("5\r\nhello\r\n0\r\n\r\n"); + stream.read(); // read 'h' + + int available = stream.available(); + + assertEquals(4, available); + } + + @Test + void skipSkipsBytes() throws IOException { + var stream = chunked("5\r\nhello\r\n0\r\n\r\n"); + + long skipped = stream.skip(3); + + assertEquals(3, skipped); + assertEquals('l', stream.read()); + assertEquals('o', stream.read()); + } + + @Test + void closeDrainsAndParsesTrailers() throws IOException { + var stream = chunked("5\r\nhello\r\n0\r\nX-Foo: bar\r\n\r\n"); + stream.close(); + + var trailers = stream.getTrailers(); + + assertEquals("bar", trailers.firstValue("x-foo")); + } + + @Test + void closeIsIdempotent() throws IOException { + var stream = chunked("5\r\nhello\r\n0\r\n\r\n"); + + stream.close(); + stream.close(); + + assertEquals(-1, stream.read()); + } + + @Test + void throwsOnInvalidHex() { + var stream = chunked("xyz\r\nhello\r\n0\r\n\r\n"); + + assertThrows(IOException.class, stream::read); + } + + @Test + void throwsOnMissingCrlf() { + var stream = chunked("5\r\nhelloX"); + + assertThrows(IOException.class, stream::readAllBytes); + } + + @Test + void throwsOnUnexpectedEofInSingleByteRead() { + var stream = chunked("5\r\nhi"); + + assertThrows(IOException.class, () -> { + stream.read(); + stream.read(); + stream.read(); // expects 5 bytes but only 2 available + }); + } + + @Test + void throwsOnUnexpectedEofInBulkRead() { + var stream = chunked("5\r\nhi"); + + assertThrows(IOException.class, () -> { + // First read returns 2 bytes, second read hits EOF + byte[] buf = new byte[10]; + stream.read(buf, 0, 10); + stream.read(buf, 0, 10); + }); + } + + @Test + void readReturnsNegativeOneAfterClose() throws IOException { + var stream = chunked("5\r\nhello\r\n0\r\n\r\n"); + stream.close(); + + assertEquals(-1, stream.read()); + } + + @Test + void readReturnsNegativeOneAfterEof() throws IOException { + var stream = chunked("0\r\n\r\n"); + stream.readAllBytes(); + + assertEquals(-1, stream.read()); + } + + @Test + void bulkReadReturnsNegativeOneAfterEof() throws IOException { + var stream = chunked("0\r\n\r\n"); + stream.readAllBytes(); + + assertEquals(-1, stream.read(new byte[10], 0, 10)); + } + + @Test + void skipReturnsZeroAfterEof() throws IOException { + var stream = chunked("0\r\n\r\n"); + stream.readAllBytes(); + + assertEquals(0, stream.skip(10)); + } + + @Test + void skipReturnsZeroWhenClosed() throws IOException { + var stream = chunked("5\r\nhello\r\n0\r\n\r\n"); + stream.close(); + + assertEquals(0, stream.skip(10)); + } + + @Test + void skipStopsAtEof() throws IOException { + var stream = chunked("3\r\nabc\r\n0\r\n\r\n"); + + long skipped = stream.skip(100); + + assertEquals(3, skipped); + } + + @Test + void availableReturnsZeroAfterEof() throws IOException { + var stream = chunked("0\r\n\r\n"); + stream.readAllBytes(); + + assertEquals(0, stream.available()); + } + + @Test + void availableReturnsZeroWhenClosed() throws IOException { + var stream = chunked("5\r\nhello\r\n0\r\n\r\n"); + stream.close(); + + assertEquals(0, stream.available()); + } + + @Test + void availableReturnsZeroBeforeFirstRead() throws IOException { + var stream = chunked("5\r\nhello\r\n0\r\n\r\n"); + + assertEquals(0, stream.available()); + } + + @Test + void throwsOnEmptyChunkSizeLine() { + var stream = chunked("\r\nhello\r\n0\r\n\r\n"); + + assertThrows(IOException.class, stream::read); + } + + @Test + void throwsOnMissingChunkSize() { + var stream = chunked(";extension\r\nhello\r\n0\r\n\r\n"); + + assertThrows(IOException.class, stream::read); + } + + @Test + void throwsOnInvalidCrlfAfterChunk() { + var stream = chunked("5\r\nhello\n\n0\r\n\r\n"); + + assertThrows(IOException.class, stream::readAllBytes); + } + + @Test + void throwsOnTruncatedCrlfAfterChunk() { + var stream = chunked("5\r\nhello\r"); + + assertThrows(IOException.class, stream::readAllBytes); + } + + @Test + void throwsOnChunkSizeExceedsMax() { + // 0x100000000 = 4GB, way over the 1MB default max + var stream = chunked("100000000\r\n"); + + assertThrows(IOException.class, stream::read); + } + + @Test + void throwsOnInvalidTrailerLine() { + // Trailer line without colon is invalid + var stream = chunked("5\r\nhello\r\n0\r\ninvalidtrailer\r\n\r\n"); + + assertThrows(IOException.class, stream::readAllBytes); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStreamTest.java new file mode 100644 index 000000000..efbf66b6c --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStreamTest.java @@ -0,0 +1,157 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpHeaders; + +class ChunkedOutputStreamTest { + + @Test + void writesSingleChunk() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new ChunkedOutputStream(delegate, 1024); + stream.write("hello".getBytes()); + stream.close(); + + assertEquals("5\r\nhello\r\n0\r\n\r\n", delegate.toString()); + } + + @Test + void writesMultipleChunks() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new ChunkedOutputStream(delegate, 5); + stream.write("hello world".getBytes()); + stream.close(); + + assertEquals("5\r\nhello\r\n5\r\n worl\r\n1\r\nd\r\n0\r\n\r\n", delegate.toString()); + } + + @Test + void writesEmptyBody() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new ChunkedOutputStream(delegate); + stream.close(); + + assertEquals("0\r\n\r\n", delegate.toString()); + } + + @Test + void writesSingleBytes() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new ChunkedOutputStream(delegate, 1024); + stream.write('a'); + stream.write('b'); + stream.write('c'); + stream.close(); + + assertEquals("3\r\nabc\r\n0\r\n\r\n", delegate.toString()); + } + + @Test + void flushWritesChunk() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new ChunkedOutputStream(delegate, 1024); + stream.write("hello".getBytes()); + stream.flush(); + + assertEquals("5\r\nhello\r\n", delegate.toString()); + } + + @Test + void writesTrailers() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new ChunkedOutputStream(delegate, 1024); + stream.write("hello".getBytes()); + stream.setTrailers(HttpHeaders.of(Map.of("x-checksum", List.of("abc123")))); + stream.close(); + + assertEquals("5\r\nhello\r\n0\r\nx-checksum: abc123\r\n\r\n", delegate.toString()); + } + + @Test + void writesMultipleTrailers() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new ChunkedOutputStream(delegate, 1024); + stream.write("hi".getBytes()); + stream.setTrailers(HttpHeaders.of(Map.of( + "x-foo", + List.of("bar"), + "x-baz", + List.of("qux")))); + stream.close(); + + String result = delegate.toString(); + assertTrue(result.startsWith("2\r\nhi\r\n0\r\n")); + assertTrue(result.contains("x-foo: bar\r\n")); + assertTrue(result.contains("x-baz: qux\r\n")); + assertTrue(result.endsWith("\r\n\r\n")); + } + + @Test + void writesMultiValueTrailer() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new ChunkedOutputStream(delegate, 1024); + stream.write("hi".getBytes()); + stream.setTrailers(HttpHeaders.of(Map.of("x-multi", List.of("a", "b")))); + stream.close(); + + String result = delegate.toString(); + assertTrue(result.contains("x-multi: a\r\n")); + assertTrue(result.contains("x-multi: b\r\n")); + } + + @Test + void singleByteWriteFlushesWhenBufferFull() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new ChunkedOutputStream(delegate, 3); + stream.write('a'); + stream.write('b'); + stream.write('c'); // buffer full, triggers flush + + assertEquals("3\r\nabc\r\n", delegate.toString()); + } + + @Test + void closeIsIdempotent() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new ChunkedOutputStream(delegate); + stream.write("hi".getBytes()); + stream.close(); + stream.close(); + + assertEquals("2\r\nhi\r\n0\r\n\r\n", delegate.toString()); + } + + @Test + void throwsAfterClose() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new ChunkedOutputStream(delegate); + stream.close(); + + assertThrows(IOException.class, () -> stream.write(1)); + } + + @Test + void throwsOnInvalidChunkSize() { + var delegate = new ByteArrayOutputStream(); + + assertThrows(IllegalArgumentException.class, () -> new ChunkedOutputStream(delegate, 0)); + } + + @Test + void throwsOnNullDelegate() { + assertThrows(NullPointerException.class, () -> new ChunkedOutputStream(null)); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/HttpUtilsTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/HttpUtilsTest.java index 33c1bea6f..199195025 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/HttpUtilsTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/HttpUtilsTest.java @@ -10,104 +10,122 @@ import static org.junit.jupiter.api.Assertions.assertSame; import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import software.amazon.smithy.java.http.api.HttpHeaders; class HttpUtilsTest { - @Test - void internHeaderReturnsInternedStringForKnownHeaders() { - var buf = "content-type".getBytes(StandardCharsets.US_ASCII); - var result = HttpUtils.internHeader(buf, 0, buf.length); - - assertSame("content-type", result); + static Stream knownHeaders() { + return Stream.of( + // GROUP_4 + Arguments.of("date"), + Arguments.of("vary"), + Arguments.of("etag"), + // GROUP_6 + Arguments.of("server"), + // GROUP_7 + Arguments.of("trailer"), + Arguments.of("expires"), + Arguments.of("upgrade"), + // GROUP_8 + Arguments.of("location"), + // GROUP_10 + Arguments.of("connection"), + Arguments.of("keep-alive"), + Arguments.of("set-cookie"), + // GROUP_12 + Arguments.of("content-type"), + // GROUP_13 + Arguments.of("cache-control"), + Arguments.of("last-modified"), + Arguments.of("content-range"), + Arguments.of("accept-ranges"), + // GROUP_14 + Arguments.of("content-length"), + // GROUP_16 + Arguments.of("content-encoding"), + Arguments.of("x-amzn-requestid"), + Arguments.of("x-amz-request-id"), + Arguments.of("www-authenticate"), + Arguments.of("proxy-connection"), + // GROUP_17 + Arguments.of("transfer-encoding"), + // GROUP_18 + Arguments.of("proxy-authenticate")); } - @Test - void internHeaderIsCaseInsensitive() { - var buf = "Content-Type".getBytes(StandardCharsets.US_ASCII); - var result = HttpUtils.internHeader(buf, 0, buf.length); + @ParameterizedTest + @MethodSource("knownHeaders") + void internsKnownHeader(String header) { + byte[] buf = header.getBytes(StandardCharsets.US_ASCII); + String result = HttpUtils.internHeader(buf, 0, buf.length); - assertSame("content-type", result); + assertSame(header, result); } - @Test - void internHeaderReturnsNewStringForUnknownHeaders() { - var buf = "x-custom-header".getBytes(StandardCharsets.US_ASCII); - var result = HttpUtils.internHeader(buf, 0, buf.length); + @ParameterizedTest + @MethodSource("knownHeaders") + void internsKnownHeaderCaseInsensitive(String header) { + byte[] buf = header.toUpperCase().getBytes(StandardCharsets.US_ASCII); + String result = HttpUtils.internHeader(buf, 0, buf.length); - assertEquals("x-custom-header", result); + assertSame(header, result); } @Test - void parseHeaderLineAddsHeader() { - var buf = "Content-Type: application/json".getBytes(StandardCharsets.US_ASCII); - var headers = HttpHeaders.ofModifiable(); - var name = HttpUtils.parseHeaderLine(buf, buf.length, headers); + void returnsNewStringForUnknownHeader() { + byte[] buf = "x-custom".getBytes(StandardCharsets.US_ASCII); + String result = HttpUtils.internHeader(buf, 0, buf.length); - assertEquals("content-type", name); - assertEquals("application/json", headers.firstValue("content-type")); + assertEquals("x-custom", result); } @Test - void parseHeaderLineTrimsWhitespace() { - var buf = "Content-Type: application/json ".getBytes(StandardCharsets.US_ASCII); - var headers = HttpHeaders.ofModifiable(); - HttpUtils.parseHeaderLine(buf, buf.length, headers); + void returnsNewStringForUnknownLengthMatch() { + // Same length as "date" but different content + byte[] buf = "test".getBytes(StandardCharsets.US_ASCII); + String result = HttpUtils.internHeader(buf, 0, buf.length); - assertEquals("application/json", headers.firstValue("content-type")); + assertEquals("test", result); } @Test - void parseHeaderLineReturnsNullForMalformedLine() { - var buf = "no-colon-here".getBytes(StandardCharsets.US_ASCII); + void parseHeaderLineReturnsNullForMissingColon() { + byte[] buf = "invalid header line".getBytes(StandardCharsets.US_ASCII); var headers = HttpHeaders.ofModifiable(); - var name = HttpUtils.parseHeaderLine(buf, buf.length, headers); + String result = HttpUtils.parseHeaderLine(buf, buf.length, headers); - assertNull(name); - } - - @Test - void parseHeaderLineHandlesEmptyValue() { - var buf = "X-Empty:".getBytes(StandardCharsets.US_ASCII); - var headers = HttpHeaders.ofModifiable(); - HttpUtils.parseHeaderLine(buf, buf.length, headers); - - assertEquals("", headers.firstValue("X-Empty")); + assertNull(result); } @Test void parseHeaderLineReturnsNullForColonAtStart() { - var buf = ": value".getBytes(StandardCharsets.US_ASCII); + byte[] buf = ": value".getBytes(StandardCharsets.US_ASCII); var headers = HttpHeaders.ofModifiable(); - var name = HttpUtils.parseHeaderLine(buf, buf.length, headers); + String result = HttpUtils.parseHeaderLine(buf, buf.length, headers); - assertNull(name); + assertNull(result); } @Test - void parseHeaderLineHandlesValueWithColons() { - var buf = "Location: http://example.com:8080/path".getBytes(StandardCharsets.US_ASCII); + void parseHeaderLineTrimsWhitespace() { + byte[] buf = "name: value ".getBytes(StandardCharsets.US_ASCII); var headers = HttpHeaders.ofModifiable(); HttpUtils.parseHeaderLine(buf, buf.length, headers); - assertEquals("http://example.com:8080/path", headers.firstValue("location")); + assertEquals("value", headers.firstValue("name")); } @Test - void parseHeaderLineHandlesTabWhitespace() { - var buf = "Content-Type:\t application/json".getBytes(StandardCharsets.US_ASCII); + void parseHeaderLineTrimsTab() { + byte[] buf = "name:\t\tvalue\t".getBytes(StandardCharsets.US_ASCII); var headers = HttpHeaders.ofModifiable(); HttpUtils.parseHeaderLine(buf, buf.length, headers); - assertEquals("application/json", headers.firstValue("content-type")); - } - - @Test - void internHeaderHandlesOffset() { - var buf = "XXXcontent-typeYYY".getBytes(StandardCharsets.US_ASCII); - var result = HttpUtils.internHeader(buf, 3, 12); - - assertSame("content-type", result); + assertEquals("value", headers.firstValue("name")); } } diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ProxyTunnelTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ProxyTunnelTest.java new file mode 100644 index 000000000..ff41401f3 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ProxyTunnelTest.java @@ -0,0 +1,181 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.client.HttpCredentials; + +class ProxyTunnelTest { + + private static final Duration TIMEOUT = Duration.ofSeconds(5); + + @Test + void establishSuccessfulTunnel() throws IOException { + var socket = new FakeSocket("HTTP/1.1 200 Connection Established\r\n\r\n"); + var result = ProxyTunnel.establish(socket, "example.com", 443, null, TIMEOUT); + + assertNotNull(result.socket()); + assertEquals(200, result.statusCode()); + + var request = socket.getRequest(); + + // CONNECT uses authority-form: CONNECT host:port HTTP/1.1 + assertTrue(request.startsWith("CONNECT example.com:443 HTTP/1.1\r\n"), "Request was: " + request); + } + + @Test + void tunnelFailsWithForbidden() throws IOException { + var socket = new FakeSocket("HTTP/1.1 403 Forbidden\r\n\r\n"); + var result = ProxyTunnel.establish(socket, "example.com", 443, null, TIMEOUT); + + assertNull(result.socket()); + assertEquals(403, result.statusCode()); + } + + @Test + void tunnelWithBasicAuth() throws IOException { + var socket = new FakeSocket("HTTP/1.1 200 Connection Established\r\n\r\n"); + var creds = new HttpCredentials.Basic("user", "pass", true); + var result = ProxyTunnel.establish(socket, "example.com", 443, creds, TIMEOUT); + + assertNotNull(result.socket()); + assertEquals(200, result.statusCode()); + + var request = socket.getRequest().toLowerCase(); + assertTrue(request.contains("proxy-authorization: basic"), "Request was: " + socket.getRequest()); + } + + @Test + void tunnelAuthFailsAfter407() throws IOException { + var socket = new FakeSocket("HTTP/1.1 407 Proxy Authentication Required\r\n\r\n"); + var creds = new HttpCredentials.Basic("user", "pass", true); + var result = ProxyTunnel.establish(socket, "example.com", 443, creds, TIMEOUT); + + assertNull(result.socket()); + assertEquals(407, result.statusCode()); + } + + @Test + void tunnelWithMultiRoundAuth() throws IOException { + var socket = new FakeSocket( + "HTTP/1.1 407 Proxy Authentication Required\r\n\r\n" + + "HTTP/1.1 200 Connection Established\r\n\r\n"); + var creds = new MultiRoundCredentials(); + var result = ProxyTunnel.establish(socket, "example.com", 443, creds, TIMEOUT); + + assertNotNull(result.socket()); + assertEquals(200, result.statusCode()); + assertEquals(2, creds.callCount); + } + + @Test + void tunnelWithoutCredentialsOn407() throws IOException { + var socket = new FakeSocket("HTTP/1.1 407 Proxy Authentication Required\r\n\r\n"); + var result = ProxyTunnel.establish(socket, "example.com", 443, null, TIMEOUT); + + assertNull(result.socket()); + assertEquals(407, result.statusCode()); + } + + @Test + void tunnelIncludesHostHeader() throws IOException { + var socket = new FakeSocket("HTTP/1.1 200 Connection Established\r\n\r\n"); + ProxyTunnel.establish(socket, "example.com", 443, null, TIMEOUT); + var request = socket.getRequest(); + + // Host header is auto-generated from URI, check for lowercase + assertTrue(request.contains("host: example.com") || request.contains("Host: example.com"), + "Request was: " + request); + } + + @Test + void tunnelIncludesProxyConnectionHeader() throws IOException { + var socket = new FakeSocket("HTTP/1.1 200 Connection Established\r\n\r\n"); + ProxyTunnel.establish(socket, "example.com", 443, null, TIMEOUT); + var request = socket.getRequest(); + + // Check for the header (case may vary) + assertTrue(request.toLowerCase().contains("proxy-connection:"), + "Request was: " + request); + } + + static class MultiRoundCredentials implements HttpCredentials { + int callCount = 0; + + @Override + public boolean authenticate(HttpRequest.Builder request, HttpResponse priorResponse) { + callCount++; + request.withAddedHeader("Proxy-Authorization", "Round" + callCount); + return true; + } + } + + static final class FakeSocket extends Socket { + private final ByteArrayInputStream in; + private final ByteArrayOutputStream out; + private final InetAddress address; + + FakeSocket(String response) throws IOException { + this.in = new ByteArrayInputStream(response.getBytes(StandardCharsets.US_ASCII)); + this.out = new ByteArrayOutputStream(); + this.address = InetAddress.getByName("127.0.0.1"); + } + + @Override + public InputStream getInputStream() { + return in; + } + + @Override + public OutputStream getOutputStream() { + return out; + } + + @Override + public InetAddress getInetAddress() { + return address; + } + + @Override + public int getPort() { + return 8080; + } + + @Override + public SocketAddress getRemoteSocketAddress() { + return null; + } + + @Override + public void setSoTimeout(int timeout) {} + + @Override + public int getSoTimeout() { + return 0; + } + + String getRequest() { + return out.toString(StandardCharsets.US_ASCII); + } + } +} From 5da98a6c49b3ef6492d85ef30679cc3341985ef6 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Fri, 5 Dec 2025 12:20:50 -0600 Subject: [PATCH 14/60] Add HPACK tests and test suite --- http/http-client/build.gradle.kts | 2 + .../client/h2/hpack/DynamicTableTest.java | 173 + .../client/h2/hpack/HpackDecoderTest.java | 172 + .../client/h2/hpack/HpackEncoderTest.java | 232 + .../client/h2/hpack/HpackTestSuiteTest.java | 116 + .../test/resources/hpack-test-case/LICENSE | 22 + .../resources/hpack-test-case/story_00.json | 59 + .../resources/hpack-test-case/story_01.json | 56 + .../resources/hpack-test-case/story_02.json | 359 + .../resources/hpack-test-case/story_03.json | 362 + .../resources/hpack-test-case/story_04.json | 362 + .../resources/hpack-test-case/story_05.json | 386 + .../resources/hpack-test-case/story_06.json | 362 + .../resources/hpack-test-case/story_07.json | 365 + .../resources/hpack-test-case/story_08.json | 383 + .../resources/hpack-test-case/story_09.json | 365 + .../resources/hpack-test-case/story_10.json | 359 + .../resources/hpack-test-case/story_11.json | 389 + .../resources/hpack-test-case/story_12.json | 389 + .../resources/hpack-test-case/story_13.json | 362 + .../resources/hpack-test-case/story_14.json | 359 + .../resources/hpack-test-case/story_15.json | 356 + .../resources/hpack-test-case/story_16.json | 395 + .../resources/hpack-test-case/story_17.json | 368 + .../resources/hpack-test-case/story_18.json | 371 + .../resources/hpack-test-case/story_19.json | 365 + .../resources/hpack-test-case/story_20.json | 6002 ++++ .../resources/hpack-test-case/story_21.json | 16154 +++++++++ .../resources/hpack-test-case/story_22.json | 15857 +++++++++ .../resources/hpack-test-case/story_23.json | 14132 ++++++++ .../resources/hpack-test-case/story_24.json | 1253 + .../resources/hpack-test-case/story_25.json | 9149 +++++ .../resources/hpack-test-case/story_26.json | 4673 +++ .../resources/hpack-test-case/story_27.json | 10328 ++++++ .../resources/hpack-test-case/story_28.json | 5549 +++ .../resources/hpack-test-case/story_29.json | 14450 ++++++++ .../resources/hpack-test-case/story_30.json | 29549 ++++++++++++++++ .../resources/hpack-test-case/story_31.json | 4673 +++ 38 files changed, 139258 insertions(+) create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTableTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoderTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoderTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackTestSuiteTest.java create mode 100644 http/http-client/src/test/resources/hpack-test-case/LICENSE create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_00.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_01.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_02.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_03.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_04.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_05.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_06.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_07.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_08.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_09.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_10.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_11.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_12.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_13.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_14.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_15.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_16.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_17.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_18.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_19.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_20.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_21.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_22.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_23.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_24.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_25.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_26.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_27.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_28.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_29.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_30.json create mode 100644 http/http-client/src/test/resources/hpack-test-case/story_31.json diff --git a/http/http-client/build.gradle.kts b/http/http-client/build.gradle.kts index c4ccc9105..9e68282cf 100644 --- a/http/http-client/build.gradle.kts +++ b/http/http-client/build.gradle.kts @@ -28,6 +28,8 @@ dependencies { // Netty for HTTP/2 integration tests testImplementation("io.netty:netty-all:4.2.7.Final") testImplementation("org.bouncycastle:bcpkix-jdk18on:1.78.1") + // Jackson for HPACK test suite JSON parsing + testImplementation("com.fasterxml.jackson.core:jackson-databind:2.18.2") // Add Apache HttpClient for benchmarking comparison jmh("org.apache.httpcomponents.client5:httpclient5:5.3.1") diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTableTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTableTest.java new file mode 100644 index 000000000..a0cf0d442 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTableTest.java @@ -0,0 +1,173 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2.hpack; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class DynamicTableTest { + + @Test + void addsEntry() { + var table = new DynamicTable(4096); + + table.add("name", "value"); + + assertEquals(1, table.length()); + assertEquals("name", table.get(62).name()); + assertEquals("value", table.get(62).value()); + } + + @Test + void calculatesEntrySize() { + // name(4) + value(5) + 32 overhead = 41 + int size = DynamicTable.entrySize("name", "value"); + + assertEquals(41, size); + } + + @Test + void tracksTotalSize() { + var table = new DynamicTable(4096); + + table.add("name", "value"); // 4 + 5 + 32 = 41 + + assertEquals(41, table.size()); + } + + @Test + void evictsOldestWhenFull() { + // Table size 100, each entry ~41 bytes, so fits 2 entries + var table = new DynamicTable(100); + + table.add("first", "value"); // 5 + 5 + 32 = 42 + table.add("second", "value"); // 6 + 5 + 32 = 43 + // Total = 85, fits + assertEquals(2, table.length()); + + table.add("third", "value"); // 5 + 5 + 32 = 42 + // Would be 127, exceeds 100, so evict oldest + + assertEquals(2, table.length()); + assertEquals("third", table.get(62).name()); + assertEquals("second", table.get(63).name()); + } + + @Test + void clearsWhenEntryTooLarge() { + var table = new DynamicTable(50); + table.add("a", "b"); // 1 + 1 + 32 = 34 + + // Entry larger than max table size + table.add("verylongname", "verylongvalue"); // 12 + 13 + 32 = 57 > 50 + + assertEquals(0, table.length()); + assertEquals(0, table.size()); + } + + @Test + void setMaxSizeEvicts() { + var table = new DynamicTable(4096); + table.add("name1", "value1"); // 5 + 6 + 32 = 43 + table.add("name2", "value2"); // 5 + 6 + 32 = 43 + assertEquals(2, table.length()); + + table.setMaxSize(50); // Only room for 1 entry + + assertEquals(1, table.length()); + assertEquals("name2", table.get(62).name()); + } + + @Test + void setMaxSizeToZeroClears() { + var table = new DynamicTable(4096); + table.add("name", "value"); + + table.setMaxSize(0); + + assertEquals(0, table.length()); + } + + @Test + void getThrowsOnInvalidIndex() { + var table = new DynamicTable(4096); + table.add("name", "value"); + + assertThrows(IndexOutOfBoundsException.class, () -> table.get(61)); // Below dynamic range + assertThrows(IndexOutOfBoundsException.class, () -> table.get(63)); // Only 1 entry at 62 + } + + @Test + void findFullMatchReturnsIndex() { + var table = new DynamicTable(4096); + table.add("first", "value1"); + table.add("second", "value2"); + + int index = table.findFullMatch("first", "value1"); + + assertEquals(63, index); // second entry (first is at 62) + } + + @Test + void findFullMatchReturnsNegativeWhenNotFound() { + var table = new DynamicTable(4096); + table.add("name", "value"); + + assertEquals(-1, table.findFullMatch("name", "other")); + assertEquals(-1, table.findFullMatch("other", "value")); + } + + @Test + void findNameMatchReturnsIndex() { + var table = new DynamicTable(4096); + table.add("first", "value1"); + table.add("second", "value2"); + + int index = table.findNameMatch("first"); + + assertEquals(63, index); + } + + @Test + void findNameMatchReturnsNegativeWhenNotFound() { + var table = new DynamicTable(4096); + table.add("name", "value"); + + assertEquals(-1, table.findNameMatch("other")); + } + + @Test + void clearRemovesAllEntries() { + var table = new DynamicTable(4096); + table.add("name1", "value1"); + table.add("name2", "value2"); + + table.clear(); + + assertEquals(0, table.length()); + assertEquals(0, table.size()); + } + + @Test + void maxSizeReturnsConfiguredMax() { + var table = new DynamicTable(1234); + + assertEquals(1234, table.maxSize()); + } + + @Test + void indicesShiftOnAdd() { + var table = new DynamicTable(4096); + table.add("first", "v"); + assertEquals(62, table.findFullMatch("first", "v")); + + table.add("second", "v"); + assertEquals(62, table.findFullMatch("second", "v")); + assertEquals(63, table.findFullMatch("first", "v")); // shifted + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoderTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoderTest.java new file mode 100644 index 000000000..d8b6f639d --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoderTest.java @@ -0,0 +1,172 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2.hpack; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; +import org.junit.jupiter.api.Test; + +class HpackDecoderTest { + + @Test + void decodesIndexedNameFromDynamicTable() throws IOException { + var encoder = new HpackEncoder(4096); + var decoder = new HpackDecoder(4096); + + // First block - add custom header to dynamic table + var out1 = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out1); + encoder.encodeHeader(out1, "x-custom-name", "value1", false); + decoder.decode(out1.toByteArray()); + + // Second block - use indexed name from dynamic table with new value + var out2 = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out2); + encoder.encodeHeader(out2, "x-custom-name", "value2", false); + List headers = decoder.decode(out2.toByteArray()); + + assertEquals(1, headers.size()); + assertEquals("x-custom-name", headers.getFirst().name()); + assertEquals("value2", headers.getFirst().value()); + } + + @Test + void throwsOnStringLengthExceedsBuffer() { + // Craft a malformed HPACK block: literal with indexing, new name + // 0x40 = literal with indexing, name index 0 (new name) + // 0x05 = string length 5 (but we only provide 2 bytes) + byte[] malformed = {0x40, 0x05, 'a', 'b'}; + var decoder = new HpackDecoder(4096); + + assertThrows(IOException.class, () -> decoder.decode(malformed)); + } + + @Test + void throwsOnDynamicTableSizeUpdateAfterHeader() { + // Craft: indexed header (0x82 = :method GET), then table size update (0x20) + byte[] malformed = {(byte) 0x82, 0x20}; + var decoder = new HpackDecoder(4096); + + IOException ex = assertThrows(IOException.class, () -> decoder.decode(malformed)); + assertTrue(ex.getMessage().contains("beginning of header block")); + } + + @Test + void throwsOnHeaderListExceedsMaxSize() { + // Create decoder with small max header list size + var decoder = new HpackDecoder(4096, 50); + + // Encode a header that exceeds the limit (name + value + 32 overhead) + var encoder = new HpackEncoder(4096); + var out = new ByteArrayOutputStream(); + try { + encoder.beginHeaderBlock(out); + encoder.encodeHeader(out, "x-long-header-name", "this-is-a-long-value", false); + } catch (IOException e) { + throw new RuntimeException(e); + } + + IOException ex = assertThrows(IOException.class, () -> decoder.decode(out.toByteArray())); + assertTrue(ex.getMessage().contains("exceeds maximum size")); + } + + @Test + void throwsOnUppercaseHeaderName() { + // Craft: literal without indexing (0x00), name length 4, "Test" (uppercase T) + // 0x00 = literal without indexing, name index 0 + // 0x04 = string length 4, no huffman + // "Test" = uppercase T + // 0x05 = value length 5 + // "value" + byte[] malformed = {0x00, 0x04, 'T', 'e', 's', 't', 0x05, 'v', 'a', 'l', 'u', 'e'}; + var decoder = new HpackDecoder(4096); + + IOException ex = assertThrows(IOException.class, () -> decoder.decode(malformed)); + assertTrue(ex.getMessage().contains("uppercase")); + } + + @Test + void allowsTableSizeUpdateAtBeginning() throws IOException { + // Table size update (0x3f 0x01 = size 32) followed by indexed header + // 0x20 | 0x1f = 0x3f means size >= 31, next byte 0x01 means size = 31 + 1 = 32 + // Actually simpler: 0x20 = table size 0 (just 0x20 with 5-bit prefix) + byte[] valid = {0x20, (byte) 0x82}; // table size 0, then :method GET + var decoder = new HpackDecoder(4096); + List headers = decoder.decode(valid); + + assertEquals(1, headers.size()); + assertEquals(":method", headers.getFirst().name()); + assertEquals("GET", headers.getFirst().value()); + } + + @Test + void decodesLiteralNeverIndexed() throws IOException { + // 0x10 = literal never indexed, name index 0 + // 0x04 = name length 4 + // "test" + // 0x05 = value length 5 + // "value" + byte[] data = {0x10, 0x04, 't', 'e', 's', 't', 0x05, 'v', 'a', 'l', 'u', 'e'}; + var decoder = new HpackDecoder(4096); + List headers = decoder.decode(data); + + assertEquals(1, headers.size()); + assertEquals("test", headers.getFirst().name()); + assertEquals("value", headers.getFirst().value()); + } + + @Test + void decodesLiteralWithoutIndexing() throws IOException { + // 0x00 = literal without indexing, name index 0 + byte[] data = {0x00, 0x04, 't', 'e', 's', 't', 0x05, 'v', 'a', 'l', 'u', 'e'}; + var decoder = new HpackDecoder(4096); + List headers = decoder.decode(data); + + assertEquals(1, headers.size()); + assertEquals("test", headers.getFirst().name()); + assertEquals("value", headers.getFirst().value()); + } + + @Test + void throwsOnInvalidIndex() { + // 0x80 = indexed with index 0 (invalid) + byte[] malformed = {(byte) 0x80}; + var decoder = new HpackDecoder(4096); + + assertThrows(IOException.class, () -> decoder.decode(malformed)); + } + + @Test + void throwsOnIncompleteInteger() { + // 0xff = indexed with index >= 127, needs continuation byte + byte[] malformed = {(byte) 0xff}; + var decoder = new HpackDecoder(4096); + + assertThrows(IOException.class, () -> decoder.decode(malformed)); + } + + @Test + void throwsOnIntegerOverflow() { + // Craft an integer that would overflow (too many continuation bytes) + byte[] malformed = { + (byte) 0xff, // indexed, index >= 127 + (byte) 0xff, + (byte) 0xff, + (byte) 0xff, + (byte) 0xff, + (byte) 0x0f + }; + + var decoder = new HpackDecoder(4096); + + assertThrows(IOException.class, () -> decoder.decode(malformed)); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoderTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoderTest.java new file mode 100644 index 000000000..be7bae901 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoderTest.java @@ -0,0 +1,232 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2.hpack; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; +import org.junit.jupiter.api.Test; + +class HpackEncoderTest { + + @Test + void encodesStaticIndexedHeader() throws IOException { + var encoder = new HpackEncoder(4096); + var out = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out); + encoder.encodeHeader(out, ":method", "GET", false); + + var decoder = new HpackDecoder(4096); + List headers = decoder.decode(out.toByteArray()); + + assertEquals(1, headers.size()); + assertEquals(":method", headers.getFirst().name()); + assertEquals("GET", headers.getFirst().value()); + } + + @Test + void encodesLiteralWithIndexing() throws IOException { + var encoder = new HpackEncoder(4096); + var out = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out); + encoder.encodeHeader(out, "x-custom", "value", false); + + var decoder = new HpackDecoder(4096); + List headers = decoder.decode(out.toByteArray()); + + assertEquals(1, headers.size()); + assertEquals("x-custom", headers.getFirst().name()); + assertEquals("value", headers.getFirst().value()); + } + + @Test + void encodesMultipleHeaders() throws IOException { + var encoder = new HpackEncoder(4096); + var out = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out); + encoder.encodeHeader(out, ":method", "GET", false); + encoder.encodeHeader(out, ":path", "/", false); + encoder.encodeHeader(out, ":scheme", "https", false); + + var decoder = new HpackDecoder(4096); + List headers = decoder.decode(out.toByteArray()); + + assertEquals(3, headers.size()); + assertEquals(":method", headers.get(0).name()); + assertEquals("GET", headers.get(0).value()); + assertEquals(":path", headers.get(1).name()); + assertEquals("/", headers.get(1).value()); + assertEquals(":scheme", headers.get(2).name()); + assertEquals("https", headers.get(2).value()); + } + + @Test + void reusesDynamicTableEntry() throws IOException { + var encoder = new HpackEncoder(4096); + var decoder = new HpackDecoder(4096); + + // First block - adds to dynamic table + var out1 = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out1); + encoder.encodeHeader(out1, "x-custom", "value", false); + decoder.decode(out1.toByteArray()); + int firstSize = out1.size(); + + // Second block - should use indexed from dynamic table + var out2 = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out2); + encoder.encodeHeader(out2, "x-custom", "value", false); + List headers = decoder.decode(out2.toByteArray()); + int secondSize = out2.size(); + + assertEquals(1, headers.size()); + assertEquals("x-custom", headers.getFirst().name()); + assertEquals("value", headers.getFirst().value()); + // Second encoding should be smaller (indexed reference) + assertTrue(secondSize < firstSize); + } + + @Test + void encodesSensitiveHeaderNeverIndexed() throws IOException { + var encoder = new HpackEncoder(4096); + var decoder = new HpackDecoder(4096); + + // First block - sensitive header + var out1 = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out1); + encoder.encodeHeader(out1, "x-secret", "password", true); + decoder.decode(out1.toByteArray()); + + // Second block - same header should NOT be indexed + var out2 = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out2); + encoder.encodeHeader(out2, "x-secret", "password", true); + List headers = decoder.decode(out2.toByteArray()); + + assertEquals(1, headers.size()); + assertEquals("x-secret", headers.getFirst().name()); + // Size should be same (not indexed) + assertEquals(out1.size(), out2.size()); + } + + @Test + void authorizationHeaderNeverIndexed() throws IOException { + var encoder = new HpackEncoder(4096); + var decoder = new HpackDecoder(4096); + + // First block + var out1 = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out1); + encoder.encodeHeader(out1, "authorization", "Bearer token", false); + decoder.decode(out1.toByteArray()); + + // Second block - should NOT be indexed even though sensitive=false + var out2 = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out2); + encoder.encodeHeader(out2, "authorization", "Bearer token", false); + List headers = decoder.decode(out2.toByteArray()); + + assertEquals(1, headers.size()); + assertEquals("authorization", headers.getFirst().name()); + // Size should be same (not indexed) + assertEquals(out1.size(), out2.size()); + } + + @Test + void encodesWithoutHuffman() throws IOException { + var encoder = new HpackEncoder(4096, false); + var out = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out); + encoder.encodeHeader(out, "x-test", "hello", false); + + var decoder = new HpackDecoder(4096); + List headers = decoder.decode(out.toByteArray()); + + assertEquals(1, headers.size()); + assertEquals("x-test", headers.getFirst().name()); + assertEquals("hello", headers.getFirst().value()); + } + + @Test + void emitsTableSizeUpdate() throws IOException { + var encoder = new HpackEncoder(4096); + var decoder = new HpackDecoder(4096); + + // Change table size + encoder.setMaxTableSize(2048); + + var out = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out); + encoder.encodeHeader(out, ":method", "GET", false); + + // Decoder should handle the table size update + List headers = decoder.decode(out.toByteArray()); + + assertEquals(1, headers.size()); + assertEquals(":method", headers.getFirst().name()); + } + + @Test + void tableSizeUpdateOnlyEmittedOnce() throws IOException { + var encoder = new HpackEncoder(4096); + var decoder = new HpackDecoder(4096); + + encoder.setMaxTableSize(2048); + + // First block - should emit table size update + var out1 = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out1); + encoder.encodeHeader(out1, ":method", "GET", false); + decoder.decode(out1.toByteArray()); + int firstSize = out1.size(); + + // Second block - should NOT emit table size update again + var out2 = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out2); + encoder.encodeHeader(out2, ":method", "GET", false); + decoder.decode(out2.toByteArray()); + int secondSize = out2.size(); + + // Second should be smaller (no table size update prefix) + assertTrue(secondSize < firstSize); + } + + @Test + void encodesLargeInteger() throws IOException { + var encoder = new HpackEncoder(4096); + var out = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out); + // Use a long value that requires multi-byte integer encoding + String longValue = "x".repeat(200); + encoder.encodeHeader(out, "x-long", longValue, false); + + var decoder = new HpackDecoder(4096); + List headers = decoder.decode(out.toByteArray()); + + assertEquals(1, headers.size()); + assertEquals("x-long", headers.getFirst().name()); + assertEquals(longValue, headers.getFirst().value()); + } + + @Test + void encodesStaticNameWithNewValue() throws IOException { + var encoder = new HpackEncoder(4096); + var out = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out); + // :path is in static table, but /custom is not + encoder.encodeHeader(out, ":path", "/custom/path", false); + + var decoder = new HpackDecoder(4096); + List headers = decoder.decode(out.toByteArray()); + + assertEquals(1, headers.size()); + assertEquals(":path", headers.getFirst().name()); + assertEquals("/custom/path", headers.getFirst().value()); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackTestSuiteTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackTestSuiteTest.java new file mode 100644 index 000000000..b1b45463b --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackTestSuiteTest.java @@ -0,0 +1,116 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2.hpack; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * HPACK decoder test suite using test vectors from http2jp/hpack-test-case. + * + *

    These test vectors are from the nghttp2 implementation and cover various + * HPACK encoding scenarios including Huffman encoding, dynamic table operations, + * and indexed headers. + * + * @see hpack-test-case + */ +class HpackTestSuiteTest { + + // Cache decoders per story file to maintain dynamic table state across cases + private static final Map DECODERS = new ConcurrentHashMap<>(); + private static final ObjectMapper MAPPER = new ObjectMapper(); + + static Stream hpackTestCases() throws IOException { + List args = new ArrayList<>(); + + for (int i = 0; i <= 31; i++) { + String filename = String.format("hpack-test-case/story_%02d.json", i); + InputStream is = HpackTestSuiteTest.class.getClassLoader().getResourceAsStream(filename); + if (is == null) { + continue; + } + + JsonNode root = MAPPER.readTree(is); + JsonNode cases = root.get("cases"); + if (cases == null) { + continue; + } + + for (JsonNode testCase : cases) { + int seqno = testCase.get("seqno").asInt(); + String wire = testCase.get("wire").asText(); + JsonNode headers = testCase.get("headers"); + + List expectedHeaders = new ArrayList<>(); + for (JsonNode header : headers) { + var fields = header.fields(); + while (fields.hasNext()) { + var field = fields.next(); + expectedHeaders.add(new String[] {field.getKey(), field.getValue().asText()}); + } + } + + args.add(Arguments.of(filename, seqno, wire, expectedHeaders)); + } + } + + return args.stream(); + } + + @ParameterizedTest(name = "{0} case {1}") + @MethodSource("hpackTestCases") + void decodeTestCase(String filename, int seqno, String wireHex, List expectedHeaders) + throws IOException { + // Each story uses a shared decoder to maintain dynamic table state across cases + HpackDecoder decoder = getDecoderForStory(filename); + + // Decode this case's wire bytes + byte[] wireBytes = hexToBytes(wireHex); + List result = decoder.decode(wireBytes); + + // Verify the decoded headers match expected + assertEquals(expectedHeaders.size(), + result.size(), + "Header count mismatch for " + filename + " case " + seqno); + + for (int i = 0; i < expectedHeaders.size(); i++) { + String[] expected = expectedHeaders.get(i); + HpackDecoder.HeaderField actual = result.get(i); + + assertEquals(expected[0], + actual.name(), + "Header name mismatch at index " + i + " for " + filename + " case " + seqno); + assertEquals(expected[1], + actual.value(), + "Header value mismatch at index " + i + " for " + filename + " case " + seqno); + } + } + + private HpackDecoder getDecoderForStory(String filename) { + return DECODERS.computeIfAbsent(filename, k -> new HpackDecoder(4096)); + } + + private static byte[] hexToBytes(String hex) { + int len = hex.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16)); + } + return data; + } +} diff --git a/http/http-client/src/test/resources/hpack-test-case/LICENSE b/http/http-client/src/test/resources/hpack-test-case/LICENSE new file mode 100644 index 000000000..91f2da1c5 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/LICENSE @@ -0,0 +1,22 @@ +The test vectors in this directory are from the hpack-test-case project: +https://github.com/http2jp/hpack-test-case + +Copyright (c) 2013 HTTP/2 Japan Community + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/http/http-client/src/test/resources/hpack-test-case/story_00.json b/http/http-client/src/test/resources/hpack-test-case/story_00.json new file mode 100644 index 000000000..9546126cf --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_00.json @@ -0,0 +1,59 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "82864188f439ce75c875fa5784", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yahoo.co.jp" + }, + { + ":path": "/" + } + ] + }, + { + "seqno": 1, + "wire": "8286418cf1e3c2fe8739ceb90ebf4aff84", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.yahoo.co.jp" + }, + { + ":path": "/" + } + ] + }, + { + "seqno": 2, + "wire": "82864187eabfa35332fd2b049b60d48e62a1849eb611589825353141e63ad52160b206c4f2f5d537", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/top/sp2/cmn/logo-ns-130528.png" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_01.json b/http/http-client/src/test/resources/hpack-test-case/story_01.json new file mode 100644 index 000000000..ad7fca2b7 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_01.json @@ -0,0 +1,56 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "8741882f91d35d055c87a784827a879eb193aac92a136087f3e7cf9f3e7c874086f2b4e5a283ff84f07b2893", + "headers": [ + { + ":scheme": "https" + }, + { + ":authority": "example.com" + }, + { + ":path": "/" + }, + { + ":method": "GET" + }, + { + "user-agent": "hpack-test" + }, + { + "cookie": "xxxxxxx1" + }, + { + "x-hello": "world" + } + ] + }, + { + "seqno": 1, + "wire": "87c18482c06087f3e7cf9f3e7c8b", + "headers": [ + { + ":scheme": "https" + }, + { + ":authority": "example.com" + }, + { + ":path": "/" + }, + { + ":method": "GET" + }, + { + "user-agent": "hpack-test" + }, + { + "cookie": "xxxxxxx2" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_02.json b/http/http-client/src/test/resources/hpack-test-case/story_02.json new file mode 100644 index 000000000..de61e4470 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_02.json @@ -0,0 +1,359 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "828641871d23f67a9721e9847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "amazon.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 1, + "wire": "82864191996293cae6a473150b0e91fb3d4b90f4ff04ab60d48e62a18c4c002c4d51d88ca321ea62e94643d5babb0c92adc372c00af17168017c0cb6cb712f5d537fc2539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc190c073919d29aee30c78f1e171d23f67a9721e963f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "g-ecx.images-amazon.com" + }, + { + ":path": "/images/G/01/gno/beacon/BeaconSprite-US-01._V401903535_.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.amazon.com/" + } + ] + }, + { + "seqno": 2, + "wire": "8286c004ad60d48e62a18c4c002c795a83907415821e9a4f5309b07522b1d85a92b566f25a178b8b2f38fb4269c6a25e634bc4bfc290c1be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "g-ecx.images-amazon.com" + }, + { + ":path": "/images/G/01/x-locale/common/transparent-pixel._V386942464_.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.amazon.com/" + } + ] + }, + { + "seqno": 3, + "wire": "8286c004bf60d48e62a18c4c002c1a9982260e99cb63121903424b62d61683165619001621e8b69a9840ea93d2d61683165899003cbadaf171680071e7da7c312f5d537fc4bfc290c1be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "g-ecx.images-amazon.com" + }, + { + ":path": "/images/G/01/img12/other/disaster-relief/300-column/sandy-relief_300x75._V400689491_.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.amazon.com/" + } + ] + }, + { + "seqno": 4, + "wire": "8286418bf1e3c2e3a47ecf52e43d3f84c5c4c390c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.amazon.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 5, + "wire": "8286c104ad60d48e62a18c4c002c795a83907415821e9a4f5309b07522b1d85a92b566f25a178b885f109969c75b89798d2fc5c0c390c2bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "g-ecx.images-amazon.com" + }, + { + ":path": "/images/G/01/x-locale/common/transparent-pixel._V192234675_.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.amazon.com/" + } + ] + }, + { + "seqno": 6, + "wire": "8286c104c160d48e62a18c4c002c1a9982261139ca86103a0a888bdcb5250c0431547eec040c82284842a107b0c546bdbab46a8b172b0d34e95e2e2d000e09c7db044bcc697fc5c0c390c2bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "g-ecx.images-amazon.com" + }, + { + ":path": "/images/G/01/img12/shoes/sales_events/11_nov/1030_AccessoriesPROMO_GWright._V400626950_.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.amazon.com/" + } + ] + }, + { + "seqno": 7, + "wire": "8286c104ac60d48e62a18c4c002c436a4f49d26ee562c3a4e862fdb60c85a287000882202f1710be2101a75c6a25fa5737c5c0c390c2bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "g-ecx.images-amazon.com" + }, + { + ":path": "/images/G/01/Automotive/rotos/Duracell600_120._V192204764_.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.amazon.com/" + } + ] + }, + { + "seqno": 8, + "wire": "8286c104b060d48e62a18c4c002c5a662838e4c9548620d27b10c5071c992a90c41a4f62d40ec98abc5c42f882fb6d3c089798d2ffc5c0c390c2bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "g-ecx.images-amazon.com" + }, + { + ":path": "/images/G/01/ui/loadIndicators/loadIndicator-large._V192195480_.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.amazon.com/" + } + ] + }, + { + "seqno": 9, + "wire": "8286418f293cae6a473150b0e91fb3d4b90f4f049a60d48e62a18c8c341c7fab69beb6ee19d78b7670b2dc4bf4ae6fc6c1c490c3c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ecx.images-amazon.com" + }, + { + ":path": "/images/I/41HZ-ND-SUL._SL135_.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.amazon.com/" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_03.json b/http/http-client/src/test/resources/hpack-test-case/story_03.json new file mode 100644 index 000000000..e68346c43 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_03.json @@ -0,0 +1,362 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "828641878c6692d5c87a7f847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "baidu.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 1, + "wire": "8286c204896251f7310f52e621ffc1c0bf90be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "baidu.com" + }, + { + ":path": "/favicon.ico" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 2, + "wire": "8286418af1e3c2f18cd25ab90f4f84c2c1c090bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.baidu.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 3, + "wire": "8286be049060d4ccc4633496c48f541e6385798d2fc2539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc190c073909d29aee30c78f1e178c6692d5c87a58f60a4bb0e4bfc325f82eb8165c86f04182ee0042f61bd7c417305d71abcd5e0c2ddeb9871401f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.baidu.com" + }, + { + ":path": "/img/baidu_sylogo1.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.baidu.com/" + }, + { + "cookie": "BAIDUID=B6136AC10EBE0A8FCD216EB64C4C1A5C:FG=1" + } + ] + }, + { + "seqno": 4, + "wire": "8286c10491608324e5626a0f18e860d4ccc4c85e634bc5c0c390c2bfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.baidu.com" + }, + { + ":path": "/cache/global/img/gs.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.baidu.com/" + }, + { + "cookie": "BAIDUID=B6136AC10EBE0A8FCD216EB64C4C1A5C:FG=1" + } + ] + }, + { + "seqno": 5, + "wire": "8286418a40578e442469311721e9049f62c63c78f0c10649cac4d41e31d0c7443091d53583a560aecaed102b817e88c653032a2f2ac590c4c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "s1.bdstatic.com" + }, + { + ":path": "/r/www/cache/global/js/tangram-1.3.4c1.0.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.baidu.com/" + } + ] + }, + { + "seqno": 6, + "wire": "8286bf049962c63c78f0c10649cac4d41e31d0c7443139e92ac15de5fa23c7bec590c4c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "s1.bdstatic.com" + }, + { + ":path": "/r/www/cache/global/js/home-1.8.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.baidu.com/" + } + ] + }, + { + "seqno": 7, + "wire": "8286bf049762c63c78f0c10649cac5a82d8c744316ac15d95da5fa23c7bec590c4c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "s1.bdstatic.com" + }, + { + ":path": "/r/www/cache/user/js/u-1.3.4.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.baidu.com/" + } + ] + }, + { + "seqno": 8, + "wire": "8286bf049162c63c78f0c1a999832c15c0b817aea9bfc7c2c590c4c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "s1.bdstatic.com" + }, + { + ":path": "/r/www/img/i-1.0.0.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.baidu.com/" + } + ] + }, + { + "seqno": 9, + "wire": "8286c304896251f7310f52e621ffc7c6c590c4c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.baidu.com" + }, + { + ":path": "/favicon.ico" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "BAIDUID=B6136AC10EBE0A8FCD216EB64C4C1A5C:FG=1" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_04.json b/http/http-client/src/test/resources/hpack-test-case/story_04.json new file mode 100644 index 000000000..e68346c43 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_04.json @@ -0,0 +1,362 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "828641878c6692d5c87a7f847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "baidu.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 1, + "wire": "8286c204896251f7310f52e621ffc1c0bf90be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "baidu.com" + }, + { + ":path": "/favicon.ico" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 2, + "wire": "8286418af1e3c2f18cd25ab90f4f84c2c1c090bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.baidu.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 3, + "wire": "8286be049060d4ccc4633496c48f541e6385798d2fc2539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc190c073909d29aee30c78f1e178c6692d5c87a58f60a4bb0e4bfc325f82eb8165c86f04182ee0042f61bd7c417305d71abcd5e0c2ddeb9871401f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.baidu.com" + }, + { + ":path": "/img/baidu_sylogo1.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.baidu.com/" + }, + { + "cookie": "BAIDUID=B6136AC10EBE0A8FCD216EB64C4C1A5C:FG=1" + } + ] + }, + { + "seqno": 4, + "wire": "8286c10491608324e5626a0f18e860d4ccc4c85e634bc5c0c390c2bfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.baidu.com" + }, + { + ":path": "/cache/global/img/gs.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.baidu.com/" + }, + { + "cookie": "BAIDUID=B6136AC10EBE0A8FCD216EB64C4C1A5C:FG=1" + } + ] + }, + { + "seqno": 5, + "wire": "8286418a40578e442469311721e9049f62c63c78f0c10649cac4d41e31d0c7443091d53583a560aecaed102b817e88c653032a2f2ac590c4c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "s1.bdstatic.com" + }, + { + ":path": "/r/www/cache/global/js/tangram-1.3.4c1.0.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.baidu.com/" + } + ] + }, + { + "seqno": 6, + "wire": "8286bf049962c63c78f0c10649cac4d41e31d0c7443139e92ac15de5fa23c7bec590c4c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "s1.bdstatic.com" + }, + { + ":path": "/r/www/cache/global/js/home-1.8.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.baidu.com/" + } + ] + }, + { + "seqno": 7, + "wire": "8286bf049762c63c78f0c10649cac5a82d8c744316ac15d95da5fa23c7bec590c4c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "s1.bdstatic.com" + }, + { + ":path": "/r/www/cache/user/js/u-1.3.4.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.baidu.com/" + } + ] + }, + { + "seqno": 8, + "wire": "8286bf049162c63c78f0c1a999832c15c0b817aea9bfc7c2c590c4c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "s1.bdstatic.com" + }, + { + ":path": "/r/www/img/i-1.0.0.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.baidu.com/" + } + ] + }, + { + "seqno": 9, + "wire": "8286c304896251f7310f52e621ffc7c6c590c4c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.baidu.com" + }, + { + ":path": "/favicon.ico" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "BAIDUID=B6136AC10EBE0A8FCD216EB64C4C1A5C:FG=1" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_05.json b/http/http-client/src/test/resources/hpack-test-case/story_05.json new file mode 100644 index 000000000..a76072258 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_05.json @@ -0,0 +1,386 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "8286418d98a75c960cd32283212b9ec9bf847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff609a251147043745773468a1a9f168774355636f5f3e534fbf4370ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "geo.craigslist.org" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "cl_b=AB2BKbsl4hGM7M4nH5PYWghTM5A" + } + ] + }, + { + "seqno": 1, + "wire": "8286418df1e3c2e4b066991419095cf64d048960719ed4b08324a863c3c2c190c0bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.craigslist.org" + }, + { + ":path": "/about/sites/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "cl_b=AB2BKbsl4hGM7M4nH5PYWghTM5A" + } + ] + }, + { + "seqno": 2, + "wire": "8286be048f6109f54150c10f6d49b0c542e4423fc3538e497ca582211f5f2c7cfdf6800b87c290c1739b9d29aee30c78f1e17258334c8a0c84ae7b2660719ed4b08324a863c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.craigslist.org" + }, + { + ":path": "/styles/countries.css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.craigslist.org/about/sites/" + }, + { + "cookie": "cl_b=AB2BKbsl4hGM7M4nH5PYWghTM5A" + } + ] + }, + { + "seqno": 3, + "wire": "8286c0048a63a21894f65234a17e88c553032a2f2ac490c3bfc2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.craigslist.org" + }, + { + ":path": "/js/formats.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.craigslist.org/about/sites/" + }, + { + "cookie": "cl_b=AB2BKbsl4hGM7M4nH5PYWghTM5A" + } + ] + }, + { + "seqno": 4, + "wire": "8286c1048f63a218e9dad2d9e960aed2e25fa23fc6bec490c3bfc2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.craigslist.org" + }, + { + ":path": "/js/jquery-1.4.2.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.craigslist.org/about/sites/" + }, + { + "cookie": "cl_b=AB2BKbsl4hGM7M4nH5PYWghTM5A" + } + ] + }, + { + "seqno": 5, + "wire": "8286c104896251f7310f52e621ffc6539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc590c4c3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.craigslist.org" + }, + { + ":path": "/favicon.ico" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "cl_b=AB2BKbsl4hGM7M4nH5PYWghTM5A" + } + ] + }, + { + "seqno": 6, + "wire": "8286418f44e71d085c960cd32283212b9ec9bf84c8c7c690c5c1c4", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "shoals.craigslist.org" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.craigslist.org/about/sites/" + }, + { + "cookie": "cl_b=AB2BKbsl4hGM7M4nH5PYWghTM5A" + } + ] + }, + { + "seqno": 7, + "wire": "8286c3048f6109f54150c12c19a6450642572211c8c2c690c573959d29aee30c22738e842e4b066991419095cf64cc7f60b2251147043745773468a1a9f168774355636f5f3e534fbf4370fda84a2290b2c540ea9a02d5f6a1288a42cb14f5c089ce3a11", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.craigslist.org" + }, + { + ":path": "/styles/craigslist.css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://shoals.craigslist.org/" + }, + { + "cookie": "cl_b=AB2BKbsl4hGM7M4nH5PYWghTM5A; cl_def_lang=en; cl_def_hp=shoals" + } + ] + }, + { + "seqno": 8, + "wire": "8286c5048a63a21894f65234a17e88cac2c890c7bfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.craigslist.org" + }, + { + ":path": "/js/formats.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://shoals.craigslist.org/" + }, + { + "cookie": "cl_b=AB2BKbsl4hGM7M4nH5PYWghTM5A; cl_def_lang=en; cl_def_hp=shoals" + } + ] + }, + { + "seqno": 9, + "wire": "8286c5048b63a2189cf496b1cc55fa23cac2c890c7bfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.craigslist.org" + }, + { + ":path": "/js/homepage.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://shoals.craigslist.org/" + }, + { + "cookie": "cl_b=AB2BKbsl4hGM7M4nH5PYWghTM5A; cl_def_lang=en; cl_def_hp=shoals" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_06.json b/http/http-client/src/test/resources/hpack-test-case/story_06.json new file mode 100644 index 000000000..47272301f --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_06.json @@ -0,0 +1,362 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "828641862c63f4b90f4f847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ebay.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 1, + "wire": "82864189f1e3c2e58c7e9721e984c2c1c090bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.ebay.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 2, + "wire": "8286418b2c63f4b2127b0c542e43d3049b63c56b10f524b5258b6ba0e3910c080113010b1910759c6d7e95cdc3539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc290c1738f9d29aee30c78f1e172c63f4b90f4b1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ebay-stories.com" + }, + { + ":path": "/wp-content/uploads/2012/11/Iso-65.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.ebay.com/" + } + ] + }, + { + "seqno": 3, + "wire": "8286418ab0fdcb62e58c7e9721e9048862c3f72d88f55118c6c0c490c3bf60ffa3012c63f502ade04472aacdf544caade0fb524ac30475c72b0a89978000000000000036275c6c0d49ffe5a1ad8d982ecbf80bb0fe05f87643f3f2d89d71b03527ff9f6a11089a0238ebcf332842c8c036dc7dc68ae34e11c96518c238cbf6a220bd3437da86f5dd9452de9e7ef9b3aafcdeff7a60f3a0583c73dfc05ab7f307eefe60d34e817ed3fb3f3df867e74f0bbba6879f0cbfb6efdfc3c6adfc3cf3169eb9fa436e8dcd7bcfd300746e6bde7e90dba0ba7fdbb9707cfda951ea4150831ea82f4d0e1d10dd9f74945dd3a77c2de9df87a7316cb745e6bce7e982dd1bf6379fa68b745e6bcc3a0f0e4c353b8fa87a69e846b55fd34e8df83df3df767d3bf9b7a7a6da34f4d82e7eff69fda70cfa3961e9a365fcf0c387651bb5f1d1f8f5adfebdf3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "rover.ebay.com" + }, + { + ":path": "/roversync/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.ebay.com/" + }, + { + "cookie": "ebay=%5Esbf%3D%23%5E; dp1=bpbf/%238000000000005276504d^u1p/QEBfX0BAX19AQA**5276504d^; cssg=c67883f113a0a56964e646c6ffaa1abe; s=CgAD4ACBQlm5NYzY3ODgzZjExM2EwYTU2OTY0ZTY0NmM2ZmZhYTFhYmUBSgAYUJZuTTUwOTUxY2NkLjAuMS4zLjE1MS4zLjAuMeN+7JE*; nonsession=CgAFMABhSdlBNNTA5NTFjY2QuMC4xLjEuMTQ5LjMuMC4xAMoAIFn7Hk1jNjc4ODNmMTEzYTBhNTY5NjRlNjQ2YzZmZmFhMWFjMQDLAAFQlSPVMX8u5Z8*" + } + ] + }, + { + "seqno": 4, + "wire": "8286418bad72c63f4848d2622e43d3048a607e18acc443085e634bc8c2c690c5c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "p.ebaystatic.com" + }, + { + ":path": "/aw/pics/s.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.ebay.com/" + } + ] + }, + { + "seqno": 5, + "wire": "8286be04b5607e18acc443149eb4302004514873c94150c633d06907e98bfb9963253372297ac418b596a9ad35516ea47451105b079640bd754dc8c2c690c5c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "p.ebaystatic.com" + }, + { + ":path": "/aw/pics/mops/2012_doodles/Holiday/DS3/ImgWeek_1_Penguin_Small_150x30.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.ebay.com/" + } + ] + }, + { + "seqno": 6, + "wire": "8286be049b607e18acc443135078c746328e42d8c4a3216339fab13044bcc697c8c2c690c5c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "p.ebaystatic.com" + }, + { + ":path": "/aw/pics/globalHeader/facebook/g12.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.ebay.com/" + } + ] + }, + { + "seqno": 7, + "wire": "8286be049a607e18acc443135078c746328e42d8c27c19292d8c4c112f31a5c8c2c690c5c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "p.ebaystatic.com" + }, + { + ":path": "/aw/pics/globalHeader/twitter/g12.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.ebay.com/" + } + ] + }, + { + "seqno": 8, + "wire": "8286be04a2607e18acc443135078c746328e42d8c1887aa2a4f19a82c53583f51043e42e2f31a5c8c2c690c5c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "p.ebaystatic.com" + }, + { + ":path": "/aw/pics/globalHeader/icon_mobile_gray_11x16.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.ebay.com/" + } + ] + }, + { + "seqno": 9, + "wire": "8286418f459e57a466a972c63f562695c87a7f048362c4d3c9c3c790c6c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "srx.main.ebayrtm.com" + }, + { + ":path": "/rtm" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.ebay.com/" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_07.json b/http/http-client/src/test/resources/hpack-test-case/story_07.json new file mode 100644 index 000000000..da019c391 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_07.json @@ -0,0 +1,365 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "8286418e4246931171f55e58c9254bd454ff049a62c45845eb9eb63b898f51b1631891a72e9f16e45b8685e634bf7abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c1539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176f518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff73929d29aee30c78f1e1794642c673f55c87a58f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "static.ak.fbcdn.net" + }, + { + ":path": "/rsrc.php/v2/yb/r/GsNJNwuI-UM.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.facebook.com/" + } + ] + }, + { + "seqno": 1, + "wire": "8286c3049962c45845eb9eb63b898f5cd8b18b5e342cf5fc8dee615c8847c2538e497ca582211f5f2c7cfdf6800b87c190c0bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "static.ak.fbcdn.net" + }, + { + ":path": "/rsrc.php/v2/yY/r/u8iA3kXb8Y1.css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.facebook.com/" + } + ] + }, + { + "seqno": 2, + "wire": "8286c4049962c45845eb9eb63b898f5918b18ed0e9e3bd179b14b5ae4423c3bec190c0bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "static.ak.fbcdn.net" + }, + { + ":path": "/rsrc.php/v2/yI/r/qANVTsC52fp.css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.facebook.com/" + } + ] + }, + { + "seqno": 3, + "wire": "8286c4049a62c45845eb9eb63b898f4962c630fe8f466ed0ed9af38bd754dfc3c2c190c0bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "static.ak.fbcdn.net" + }, + { + ":path": "/rsrc.php/v2/yt/r/FZaMKqARgC6.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.facebook.com/" + } + ] + }, + { + "seqno": 4, + "wire": "8286c4049962c45845eb9eb63b898f5fac58c74a335f3fe05beb8f12fd11c353032a2f2ac290c1c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "static.ak.fbcdn.net" + }, + { + ":path": "/rsrc.php/v2/yZ/r/jlKDoX15kHG.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.facebook.com/" + } + ] + }, + { + "seqno": 5, + "wire": "8286c5049962c45845eb9eb63b898f5a98b188b46d1d95ce4bd93b2e4423c4bfc290c1c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "static.ak.fbcdn.net" + }, + { + ":path": "/rsrc.php/v2/yO/r/_MRarphcCIq.css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.facebook.com/" + } + ] + }, + { + "seqno": 6, + "wire": "8286c5049962c45845eb9eb63b898f5ad8b18bdb7a9afdfe5be40dabf447c4bec290c1c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "static.ak.fbcdn.net" + }, + { + ":path": "/rsrc.php/v2/yP/r/CRkiDDWTd1u.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.facebook.com/" + } + ] + }, + { + "seqno": 7, + "wire": "8286c5049a62c45845eb9eb63b898f5f8c79636767338671ecb39d8bd754dfc4c3c290c173ab9d29aee30c21234988b8faaf2c6492a5ea2a58b116117ae7ad8ee263d6462c63b43a78ef45e6c52d6b9108", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "static.ak.fbcdn.net" + }, + { + ":path": "/rsrc.php/v2/yX/x/Qq6L1haQrYr.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://static.ak.fbcdn.net/rsrc.php/v2/yI/r/qANVTsC52fp.css" + } + ] + }, + { + "seqno": 8, + "wire": "8286c6049962c45845eb9eb63b898f5a58b18c03b23e478a9bfc165fa23fc5bfc390c2c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "static.ak.fbcdn.net" + }, + { + ":path": "/rsrc.php/v2/yN/r/EarbWo_mDU-.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.facebook.com/" + } + ] + }, + { + "seqno": 9, + "wire": "8286c6049962c45845eb9eb63b898f4eb1e587fa25d3f1930bbed95ebaa6c5c4c390c273ab9d29aee30c21234988b8faaf2c6492a5ea2a58b116117ae7ad8ee263d6a62c622d1b47657392f64ecb9108", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "static.ak.fbcdn.net" + }, + { + ":path": "/rsrc.php/v2/y7/x/9jt7oVdF7z3.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://static.ak.fbcdn.net/rsrc.php/v2/yO/r/_MRarphcCIq.css" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_08.json b/http/http-client/src/test/resources/hpack-test-case/story_08.json new file mode 100644 index 000000000..2bb1f24e1 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_08.json @@ -0,0 +1,383 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "82864188968313ad8b90f4ff847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "flickr.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 1, + "wire": "8286418bf1e3c2f2d06275b1721e9f84c2c1c090bf60a9bbf9011f7ec73a56f3e376a3fc47033f0883b35f6a50720e837b1a4c7aa02d4b5a8559bb6a1566eda8", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.flickr.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "BX=c99r6jp89a7no&b=3&s=q4; localization=en-us%3Bus%3Bus" + } + ] + }, + { + "seqno": 2, + "wire": "8286418fb50b8e4416cee5b17f439ce75c87a704022f61c453032a2f2ac390c273919d29aee30c78f1e17968313ad8b90f4b1f60e5bb03548aced6b6f3e36c0efc47033f08803dfed4eb177320c9803f6a68dd7a04c0165b0bed3ac841f9f6a5ec704335dd946dd93437fc5fc90c31dfdd0c38acc93437ebb724309ec3430e7d1b268614032430bb7af430e50689a1ba767ed4b488823a868801", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "us.adserver.yahoo.com" + }, + { + ":path": "/a" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.flickr.com/" + }, + { + "cookie": "B=4m2rqu589a507&b=3&s=1v; k_visit=1; MSC=t=1351947310X; CH=AgBQlRQgADwDIAAbDSAAGrIgADpuIAAoriAALMQgAAs0IAA7CCAAJ0MgABo3; ucs=bnas=0" + } + ] + }, + { + "seqno": 3, + "wire": "8286c3049b60d48e62a1844e3b0ab2673216310f5216457619255ebaa65fbb9fc7539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc690c5c3c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.flickr.com" + }, + { + ":path": "/images/share-this-icons-sprite.png.v6" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "BX=c99r6jp89a7no&b=3&s=q4; localization=en-us%3Bus%3Bus" + }, + { + "referer": "http://www.flickr.com/" + } + ] + }, + { + "seqno": 4, + "wire": "8286c4049460d48e62a18968313ad8b22bb0c92af5d532fddac8bec690c5c0c3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.flickr.com" + }, + { + ":path": "/images/flickr-sprite.png.v4" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.flickr.com/" + }, + { + "cookie": "BX=c99r6jp89a7no&b=3&s=q4; localization=en-us%3Bus%3Bus" + } + ] + }, + { + "seqno": 5, + "wire": "8286c4048d625a0750e888bdcb52579aa2ffc8bec690c560c1bbf9011f7ec73a56f3e376a3fc47033f0883b35f6a50720e837b1a4c7aa02d4b5a8559bb6a1566eda8fb53d781c958400005b702cbef38ebf005f6dc79d6db683fc1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.flickr.com" + }, + { + ":path": "/flanal_event.gne" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "BX=c99r6jp89a7no&b=3&s=q4; localization=en-us%3Bus%3Bus; ywadp10001561398679=1956875541" + }, + { + "referer": "http://www.flickr.com/" + } + ] + }, + { + "seqno": 6, + "wire": "8286418ff4b8ea1d1e9262217f439ce75c87a70486625ac8bd747fcac3c890c7c2c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "y.analytics.yahoo.com" + }, + { + ":path": "/fpc.pl" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.flickr.com/" + }, + { + "cookie": "B=4m2rqu589a507&b=3&s=1v; k_visit=1; MSC=t=1351947310X; CH=AgBQlRQgADwDIAAbDSAAGrIgADpuIAAoriAALMQgAAs0IAA7CCAAJ0MgABo3; ucs=bnas=0" + } + ] + }, + { + "seqno": 7, + "wire": "82864188917f46a665c87a7f049a60856107b6b6107b6b8a62d45b0692c914b60e6a4b52579aa2ffcbc4c990c8c3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "d.yimg.com" + }, + { + ":path": "/ce/soup/soup_generated_fragment.gne" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.flickr.com/" + } + ] + }, + { + "seqno": 8, + "wire": "8286418998a75fd0e739d721e904022f62ccc2ca90c9c4c3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "geo.yahoo.com" + }, + { + ":path": "/b" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.flickr.com/" + }, + { + "cookie": "B=4m2rqu589a507&b=3&s=1v; k_visit=1; MSC=t=1351947310X; CH=AgBQlRQgADwDIAAbDSAAGrIgADpuIAAoriAALMQgAAs0IAA7CCAAJ0MgABo3; ucs=bnas=0" + } + ] + }, + { + "seqno": 9, + "wire": "8286c8049662b9ce93a18a868190f4d27a90c34fb407c2cb2d098fcccbca90c960ff8c01bbf9011f7ec73a56f3e376a3fc47033f0883b35f6a50720e837b1a4c7aa02d4b5a8559bb6a1566eda8fb53d781c958400005b702cbef38ebf005f6dc79d6db683f6a4b445de041ed9ebfb525ac81000016dc0b2fbce3afc1b3bf709baf28bfe0f8761fba3d6818ffe4a82a0200002db8165f79c75f83fe0f8761fba3d6818ffe6cefdc26ebca2ff92f7320200002db8165f79c75f83f7a04f263dbe32efd3772efcb8b2efcb8a4664673d3fa81f2d3610cdf48c40a3475e74c97c1e747be1e756fe1e345f2072d391fcbbf2e21f26fafefe4f2904f84979baa3a7841ff1ed0179d0f3e78c1ff1ed0179d0f3e78c1ff1ed0179d0f3e78c1ff1eff8f680bce879f3c60ff8f680bce879f3c60c5", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.flickr.com" + }, + { + ":path": "/photos/nasacommons/4940913342/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "BX=c99r6jp89a7no&b=3&s=q4; localization=en-us%3Bus%3Bus; ywadp10001561398679=1956875541; fl_v=souhp; fpc10001561398679=Qvv1ikW_|aUqazlyMaa|fses10001561398679=|aUqazlyMaa|Qvv1ikW_|fvis10001561398679=Zj1odHRwJTNBJTJGJTJGd3d3LmZsaWNrci5jb20lMkYmdD0xMzUxOTUwMDc1JmI9JTJGaW5kZXhfc291cC5nbmU=|8M1871YYH0|8M1871YYH0|8M1871YYH0|8|8M1871YYH0|8M1871YYH0" + }, + { + "referer": "http://www.flickr.com/" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_09.json b/http/http-client/src/test/resources/hpack-test-case/story_09.json new file mode 100644 index 000000000..30985d919 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_09.json @@ -0,0 +1,365 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "82864189a0d5752c86a9721e9f847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "linkedin.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 1, + "wire": "8286418cf1e3c2f41aaea590d52e43d384c2c1c090bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.linkedin.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 2, + "wire": "8286418d42e45e8abac8bd0624952e43d304906104910c10f510696087a693d4c7447fc353032a2f2ac290c173929d29aee30c78f1e17a0d5752c86a9721e963", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "s.c.lnkd.licdn.com" + }, + { + ":path": "/scds/concat/common/js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.linkedin.com/" + } + ] + }, + { + "seqno": 3, + "wire": "8286c004906104910c10f510696087a693d4c1108fc5538e497ca582211f5f2c7cfdf6800b87c490c3bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "s.c.lnkd.licdn.com" + }, + { + ":path": "/scds/concat/common/css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.linkedin.com/" + } + ] + }, + { + "seqno": 4, + "wire": "8286c104906104910c10f510696087a693d4c7447fc6c0c490c3bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "s.c.lnkd.licdn.com" + }, + { + ":path": "/scds/concat/common/js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.linkedin.com/" + } + ] + }, + { + "seqno": 5, + "wire": "8286c104906104910c10f510696087a693d4c1108fc6bec490c3bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "s.c.lnkd.licdn.com" + }, + { + ":path": "/scds/concat/common/css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.linkedin.com/" + } + ] + }, + { + "seqno": 6, + "wire": "8286c104906104910c10f510696087a693d4c1108fc6bec490c3bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "s.c.lnkd.licdn.com" + }, + { + ":path": "/scds/concat/common/css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.linkedin.com/" + } + ] + }, + { + "seqno": 7, + "wire": "8286c104906104910c10f510696087a693d4c7447fc6c0c490c3bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "s.c.lnkd.licdn.com" + }, + { + ":path": "/scds/concat/common/js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.linkedin.com/" + } + ] + }, + { + "seqno": 8, + "wire": "8286c2049160750e8f493110c5471da99d360c9d4b67c6c5c490c3408cf2b585ed695092c8b783267f8bfcd19f1a535ed2f6b4a84fc060ff408c873f53160fe7bc02f88c6579a6c6dacf32591669b7c0b46524ab4a095991b79c699147fcfda9414f10ed4cf124fd4b541fce2ddbee70bf1f2c386bcf9e426e726c795dd3946cfe73da823bca29aff8b531f2aa8e59e53bb8a21736ba4b9f1ad8ee0596c2fb4f3417ee351b6404a1640fb2100df8dc6df8df76479f700571a96491c65b6c4e47fcfda997760ddbb26ad392fc1fc8fa0fcdc038079c640271c0b627df13a27ff9fb53b99064c1fcf7803f18bf9fb53f16cf916c97ef41783f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.linkedin.com" + }, + { + ":path": "/analytics/noauthtracker" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "x-requested-with": "XMLHttpRequest" + }, + { + "referer": "http://www.linkedin.com/" + }, + { + "cookie": "bcookie=\"v=2&bae845a5-83ed-4590-becf-f0f3d586432b\"; leo_auth_token=\"GST:UDbWFFpLLdcS6gHJ7NJa3XYRsc7W_gDwutbWnlWLfo7G_2Y4jfLH-H:1351948419:4b5c0f1309310a9b659b97d8960e64fdd635526b\"; JSESSIONID=\"ajax:0608630266152992729\"; visit=\"v=1&G\"; X-LI-IDC=C1" + } + ] + }, + { + "seqno": 9, + "wire": "8286c304986104910c10f4d27a98b5835333128fb9887aa2eecae621ffc8c7c690c5c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "s.c.lnkd.licdn.com" + }, + { + ":path": "/scds/common/u/img/favicon_v3.ico" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.linkedin.com/" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_10.json b/http/http-client/src/test/resources/hpack-test-case/story_10.json new file mode 100644 index 000000000..a997e2207 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_10.json @@ -0,0 +1,359 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "82864185a5152e43d3847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "msn.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 1, + "wire": "82864189f1e3c2f4a2a5c87a7f84c2c1c090bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.msn.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 2, + "wire": "8286418a1c880af4a072217a8a9f049062834760ecf4c5761a92c9521798d2ffc3539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc290c1738f9d29aee30c78f1e17a5152e43d2c7f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ads1.msads.net" + }, + { + ":path": "/library/primedns.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.msn.com/" + } + ] + }, + { + "seqno": 3, + "wire": "8286418c21e85d09e8ba16a5152e43d3048a62bb0d4964a90bcc697fc6c0c490c3bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "col.stj.s-msn.com" + }, + { + ":path": "/primedns.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.msn.com/" + } + ] + }, + { + "seqno": 4, + "wire": "8286418c8e8b574248ba16a5152e43d30494606863c146cb0660b52d6a18a07e1865f5e634bfc7c1c590c4c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "blu.stc.s-msn.com" + }, + { + ":path": "/as/wea3/i/en-us/law/39.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.msn.com/" + } + ] + }, + { + "seqno": 5, + "wire": "8286bf048a62bb0d4964a90bcc697fc7c1c590c4c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "col.stj.s-msn.com" + }, + { + ":path": "/primedns.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.msn.com/" + } + ] + }, + { + "seqno": 6, + "wire": "8286418c21e85d0922e85a9454b90f4f0495623b1841183312cac0e424e7310a88a634a25e634bc8c2c690c5c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "col.stc.s-msn.com" + }, + { + ":path": "/br/sc/i/ff/adchoices_gif2.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.msn.com/" + } + ] + }, + { + "seqno": 7, + "wire": "8286418d21e85d098c005d0b528a9721e9049c60cc3c061b66f4379c8420bae09a79c7857b08841ba26a17c4bcc697c9c3c790c6c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "col.stb00.s-msn.com" + }, + { + ":path": "/i/80/53CAC6A10B6248682CF221B24A92.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.msn.com/" + } + ] + }, + { + "seqno": 8, + "wire": "8286418d21e85d098c015d0b528a9721e9049e60cc600310b9799089c65bc18410b2db6e38f5e7840c175b65a657e95cdfcac4c890c7c3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "col.stb01.s-msn.com" + }, + { + ":path": "/i/E0/A6C312635EF0A355668C820EB5343.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.msn.com/" + } + ] + }, + { + "seqno": 9, + "wire": "8286bf04a060cc5dbac5d0e1702fc2186fb57da86172e81c69ebb7eeddbd7f060bebf4ae6fcac4c890c7c3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "col.stb00.s-msn.com" + }, + { + ":path": "/i/BB/B1F619A1AD4D4AA6B0648BDBBCDEED.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.msn.com/" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_11.json b/http/http-client/src/test/resources/hpack-test-case/story_11.json new file mode 100644 index 000000000..9adaed83c --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_11.json @@ -0,0 +1,389 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "82864188abd24d4950b90f4f847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "nytimes.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 1, + "wire": "8286418b4af59cd526c3d142e43d3f048d6359cd52769e8a18df60c9d58fc2539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc190c0739f9d29aee30c78f1e178e322e43af6f562a2f84311da8354542161002ebc000360aad7b63b60c1eff6492d9e6edf6a6bdb31e0bb76ec30e1d1543f6a6bda93357afbeeb781a72f4382182eff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "t.pointroll.com" + }, + { + ":path": "/PointRoll/Track/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.bbc.co.uk/news/business-20178000" + }, + { + "cookie": "PRbu=EzZdduhgq; PRgo=BBBAAFMnA; PRti4CD975E46CAEA=B" + } + ] + }, + { + "seqno": 2, + "wire": "8286c1048d6359cd52769e8a18df60c9d58fc5c0c390c2bfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "t.pointroll.com" + }, + { + ":path": "/PointRoll/Track/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.bbc.co.uk/news/business-20178000" + }, + { + "cookie": "PRbu=EzZdduhgq; PRgo=BBBAAFMnA; PRti4CD975E46CAEA=B" + } + ] + }, + { + "seqno": 3, + "wire": "8286418f9ac1d739888797abd24d4950b90f4f04b062b193a8e62a182210c536d09352590c3623b6a9282a18aec3f42912860400898c7af39bb96f9631a4b8682f95c8847fc6538e497ca582211f5f2c7cfdf6800b87c590c473919d29aee30c78f1e17abd24d4950b90f4b1609cdba325f8000765004001082e38069d8ca57c00147f6a0e4f24440b7f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "graphics8.nytimes.com" + }, + { + ":path": "/packages/css/multimedia/bundles/projects/2012/HPLiveDebateFlex.css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.nytimes.com/" + }, + { + "cookie": "RMID=007f010022166047bee9002b; adxcs=-" + } + ] + }, + { + "seqno": 4, + "wire": "8286c104a663a2181d75b043d349ea61141a42a273f860b4c659242c9ba8348544e7f176d351216c5fa23fc953032a2f2ac890c7c0bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "graphics8.nytimes.com" + }, + { + ":path": "/js/app/common/slideshow/embeddedSlideshowBuilder.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.nytimes.com/" + }, + { + "cookie": "RMID=007f010022166047bee9002b; adxcs=-" + } + ] + }, + { + "seqno": 5, + "wire": "8286c204a5608843005c2c209614b5308a0d215139fc3149e4b682a18450690d54d8874505b3d2e4423fcac1c890c7c0bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "graphics8.nytimes.com" + }, + { + ":path": "/css/0.1/screen/slideshow/modules/slidingGallery.css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.nytimes.com/" + }, + { + "cookie": "RMID=007f010022166047bee9002b; adxcs=-" + } + ] + }, + { + "seqno": 6, + "wire": "8286c204ae60727960d48e62a1886fee6190b0d38c0e45d90b4e38f31a79ef8b45dd1164d78f5698b3e0c3be2d444842bf4ae6cac5c890c7c0bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "graphics8.nytimes.com" + }, + { + ":path": "/adx/images/ADS/31/46/ad.314668/NYT_MBM_IPHON_LEFT_Oct11.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.nytimes.com/" + }, + { + "cookie": "RMID=007f010022166047bee9002b; adxcs=-" + } + ] + }, + { + "seqno": 7, + "wire": "8286c204af62b193a8e62a18e88629b6849a92c861b11db5494150c5761fa148943020044c63d79cddcb7cb18d25c3417cafd11fcabec890c7c0bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "graphics8.nytimes.com" + }, + { + ":path": "/packages/js/multimedia/bundles/projects/2012/HPLiveDebateFlex.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.nytimes.com/" + }, + { + "cookie": "RMID=007f010022166047bee9002b; adxcs=-" + } + ] + }, + { + "seqno": 8, + "wire": "8286c204b162b193a8e62a18e88629b6849a92c861b120d236309a8a7726c357aec3d27604008a22d05224c7aa294d45284d86ad7e88cabec890c7c0bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "graphics8.nytimes.com" + }, + { + ":path": "/packages/js/multimedia/data/FilmStripPromo/2012_election_filmstrip.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.nytimes.com/" + }, + { + "cookie": "RMID=007f010022166047bee9002b; adxcs=-" + } + ] + }, + { + "seqno": 9, + "wire": "8286c204a962b193a8e62a18e8860b4148931ea43020044c4858c692a18ee690a7426c356c4a6a29426c356b9108cac1c890c7c0bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "graphics8.nytimes.com" + }, + { + ":path": "/packages/js/elections/2012/debates/videostrip/filmstrip.css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.nytimes.com/" + }, + { + "cookie": "RMID=007f010022166047bee9002b; adxcs=-" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_12.json b/http/http-client/src/test/resources/hpack-test-case/story_12.json new file mode 100644 index 000000000..6e6349c1d --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_12.json @@ -0,0 +1,389 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "82864189acd524b615095c87a7847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "pinterest.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 1, + "wire": "82864194a4b2186b10649cab50902f59aa496c2a12b90f4f049f62dae838e4602e34c842079c65d699132eb218afcffbba5c929228d7e95cdfc2539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc190c0738f9d29aee30c566a925b0a84ae43d2c760ff0a8ab35492d8542624150883f92e5f59f455bb47a9a7c9b8cdb24ab068f5da63bf828d7e860d01e92787d8dc04f37bbfa279d29978697b7fe02f93a89e85fcb677d9ad39f8f1cc06939d0de844bf9a1f1fb05e671e6312bcda58cbef70d8f566cb33191e3369e6ce8374dd97de8b323da039b4b11e9bfbf786cd8703dc3d329d9c21a59c1d6f43041fcf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "media-cache-lt0.pinterest.com" + }, + { + ":path": "/upload/164311086374323731_DhZSfIfc_b.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://pinterest.com/" + }, + { + "cookie": "_pinterest_sess=\"eJyLMnSMyghISi53cnEMyqgo9ElPya0M1jdw9/S0tY8vycxNtfUN8TX0Dck28A9JrvQPtLVVK04tLs5MsfXM9az0C3HKicpKN/JzSa/yrQrKiswKNY3MijSJzMrI8M1KN/bNDTT1rQo08Uy3tQUAm3EkCA==\"" + } + ] + }, + { + "seqno": 2, + "wire": "8286c1049f62dae838e4602e05c65d03ad01f75b79979b6e2dda7a5fdba331628d7e95cdc5c0c390c2bfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "media-cache-lt0.pinterest.com" + }, + { + ":path": "/upload/161637074097583855_SNjDRMKe_b.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://pinterest.com/" + }, + { + "cookie": "_pinterest_sess=\"eJyLMnSMyghISi53cnEMyqgo9ElPya0M1jdw9/S0tY8vycxNtfUN8TX0Dck28A9JrvQPtLVVK04tLs5MsfXM9az0C3HKicpKN/JzSa/yrQrKiswKNY3MijSJzMrI8M1KN/bNDTT1rQo08Uy3tQUAm3EkCA==\"" + } + ] + }, + { + "seqno": 3, + "wire": "8286c1049f62dae838e4604eb2dbecbad3807990084e09a8b0de3e0ebf88bd146bf4ae6fc5c0c390c2bfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "media-cache-lt0.pinterest.com" + }, + { + ":path": "/upload/273593746083022624_FCoEkXsC_b.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://pinterest.com/" + }, + { + "cookie": "_pinterest_sess=\"eJyLMnSMyghISi53cnEMyqgo9ElPya0M1jdw9/S0tY8vycxNtfUN8TX0Dck28A9JrvQPtLVVK04tLs5MsfXM9az0C3HKicpKN/JzSa/yrQrKiswKNY3MijSJzMrI8M1KN/bNDTT1rQo08Uy3tQUAm3EkCA==\"" + } + ] + }, + { + "seqno": 4, + "wire": "8286c1049e62dae838e461b13e175971a65a13cfb2e38cc5d93ae9cb375f3146bf4ae6c5c0c390c2bfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "media-cache-lt0.pinterest.com" + }, + { + ":path": "/upload/52917364342893663_qtPmJgkx_b.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://pinterest.com/" + }, + { + "cookie": "_pinterest_sess=\"eJyLMnSMyghISi53cnEMyqgo9ElPya0M1jdw9/S0tY8vycxNtfUN8TX0Dck28A9JrvQPtLVVK04tLs5MsfXM9az0C3HKicpKN/JzSa/yrQrKiswKNY3MijSJzMrI8M1KN/bNDTT1rQo08Uy3tQUAm3EkCA==\"" + } + ] + }, + { + "seqno": 5, + "wire": "8286c1049f62dae838e4602171f6c4f882db4d0196df00a2cdeb7f2355ee98a35fa5737fc5c0c390c2bfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "media-cache-lt0.pinterest.com" + }, + { + ":path": "/upload/116952921544035902_KyTWinzm_b.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://pinterest.com/" + }, + { + "cookie": "_pinterest_sess=\"eJyLMnSMyghISi53cnEMyqgo9ElPya0M1jdw9/S0tY8vycxNtfUN8TX0Dck28A9JrvQPtLVVK04tLs5MsfXM9az0C3HKicpKN/JzSa/yrQrKiswKNY3MijSJzMrI8M1KN/bNDTT1rQo08Uy3tQUAm3EkCA==\"" + } + ] + }, + { + "seqno": 6, + "wire": "8286c1049f62dae838e4604f32d34db2e804e3aebad09b1450a5377471977c51afd2b9bfc5c0c390c2bfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "media-cache-lt0.pinterest.com" + }, + { + ":path": "/upload/283445370267774252_AttBMVfT_b.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://pinterest.com/" + }, + { + "cookie": "_pinterest_sess=\"eJyLMnSMyghISi53cnEMyqgo9ElPya0M1jdw9/S0tY8vycxNtfUN8TX0Dck28A9JrvQPtLVVK04tLs5MsfXM9az0C3HKicpKN/JzSa/yrQrKiswKNY3MijSJzMrI8M1KN/bNDTT1rQo08Uy3tQUAm3EkCA==\"" + } + ] + }, + { + "seqno": 7, + "wire": "8286c1049f62dae838e4604cba1684eb2e36fbe0136f09d8ad96fe0c726d2c51afd2b9bfc5c0c390c2bfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "media-cache-lt0.pinterest.com" + }, + { + ":path": "/upload/237142736599025827_ufDEHdRe_b.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://pinterest.com/" + }, + { + "cookie": "_pinterest_sess=\"eJyLMnSMyghISi53cnEMyqgo9ElPya0M1jdw9/S0tY8vycxNtfUN8TX0Dck28A9JrvQPtLVVK04tLs5MsfXM9az0C3HKicpKN/JzSa/yrQrKiswKNY3MijSJzMrI8M1KN/bNDTT1rQo08Uy3tQUAm3EkCA==\"" + } + ] + }, + { + "seqno": 8, + "wire": "8286c1049f62dae838e46042682fb4f3ceb8e3edb2cb2f062e1769338dbf3451afd2b9bfc5c0c390c2bfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "media-cache-lt0.pinterest.com" + }, + { + ":path": "/upload/224194887669533381_UBmi659g_b.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://pinterest.com/" + }, + { + "cookie": "_pinterest_sess=\"eJyLMnSMyghISi53cnEMyqgo9ElPya0M1jdw9/S0tY8vycxNtfUN8TX0Dck28A9JrvQPtLVVK04tLs5MsfXM9az0C3HKicpKN/JzSa/yrQrKiswKNY3MijSJzMrI8M1KN/bNDTT1rQo08Uy3tQUAm3EkCA==\"" + } + ] + }, + { + "seqno": 9, + "wire": "8286c1049e62dae838e4604eb416dc71f700cb8d3afbe07628425f73548e9146bf4ae6c5c0c390c2bfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "media-cache-lt0.pinterest.com" + }, + { + ":path": "/upload/274156696036479907_A1ezgnsj_b.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://pinterest.com/" + }, + { + "cookie": "_pinterest_sess=\"eJyLMnSMyghISi53cnEMyqgo9ElPya0M1jdw9/S0tY8vycxNtfUN8TX0Dck28A9JrvQPtLVVK04tLs5MsfXM9az0C3HKicpKN/JzSa/yrQrKiswKNY3MijSJzMrI8M1KN/bNDTT1rQo08Uy3tQUAm3EkCA==\"" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_13.json b/http/http-client/src/test/resources/hpack-test-case/story_13.json new file mode 100644 index 000000000..1b768e42d --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_13.json @@ -0,0 +1,362 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "82864185edd9721e9f847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "qq.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 1, + "wire": "8286418aa4690af324d4ccb90f4f049763c78f0c1a91cc5431dbb080113129e8a0fe292af5d537c2539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc190c0738e9d29aee30c78f1e17edd9721e963", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "mat1.gtimg.com" + }, + { + ":path": "/www/images/qq2012/followme.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.qq.com/" + } + ] + }, + { + "seqno": 2, + "wire": "8286c0049763c78f0c1a91cc5431dbb080113083a0f41e63af5d537fc4bfc290c1be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "mat1.gtimg.com" + }, + { + ":path": "/www/images/qq2012/sosologo.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.qq.com/" + } + ] + }, + { + "seqno": 3, + "wire": "8286c0049e63c78f0c1a91cc5431dbb0801131295093771d0c4830bc828ec24ebd754dc4bfc290c1be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "mat1.gtimg.com" + }, + { + ":path": "/www/images/qq2012/festival/da18search.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.qq.com/" + } + ] + }, + { + "seqno": 4, + "wire": "8286c004a063c78f0c1a91cc5431dbb0801131295093771d0c4830bd19e4f51cc06d7aea9bc4bfc290c1be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "mat1.gtimg.com" + }, + { + ":path": "/www/images/qq2012/festival/da18bodybg05.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.qq.com/" + } + ] + }, + { + "seqno": 5, + "wire": "8286c0049a63c78f0c1a91cc5431dbb080113141e63543a28882b897aea9bfc4bfc290c1be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "mat1.gtimg.com" + }, + { + ":path": "/www/images/qq2012/loginall_1.2.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.qq.com/" + } + ] + }, + { + "seqno": 6, + "wire": "8286c0049c63c78f0c1a91cc5431dbb080113033751d59ce390d54c15c2bcc697fc4bfc290c1be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "mat1.gtimg.com" + }, + { + ":path": "/www/images/qq2012/aikanLoading1.1.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.qq.com/" + } + ] + }, + { + "seqno": 7, + "wire": "8286c0049263a1fa958cc71d036364a34242b8170afd11c453032a2f2ac390c2bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "mat1.gtimg.com" + }, + { + ":path": "/joke/Koala/Qfast1.0.1.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.qq.com/" + } + ] + }, + { + "seqno": 8, + "wire": "8286c1049863c78f0c1a91cc5431dbb080113149e33505d25f085ebaa6c5c0c390c2bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "mat1.gtimg.com" + }, + { + ":path": "/www/images/qq2012/mobileNews.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.qq.com/" + } + ] + }, + { + "seqno": 9, + "wire": "8286418a35330579926a665c87a7049b63bb159888627ee1604d058085d602179c61d742d3ee89c5fa5737c6c1c490c3c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "img1.gtimg.com" + }, + { + ":path": "/v/pics/hv1/241/117/1186/77149726.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.qq.com/" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_14.json b/http/http-client/src/test/resources/hpack-test-case/story_14.json new file mode 100644 index 000000000..5e84a4674 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_14.json @@ -0,0 +1,359 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "8286418841aa1ae43d2b92af847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "sina.com.cn" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 1, + "wire": "8286418bf1e3c2e835435c87a5725584c2c1c090bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.sina.com.cn" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 2, + "wire": "8286418ca8be10ba0d50d721e95c957f049763a21879d604008820134c0801105ebc7aa5de7ad7e88fc353032a2f2ac290c173919d29aee30c78f1e1741aa1ae43d2b92a63", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "news.sina.com.cn" + }, + { + ":path": "/js/87/20121024/201218ConfTop.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.sina.com.cn/" + } + ] + }, + { + "seqno": 3, + "wire": "8286418f35495e4ace7a1741aa1ae43d2b92af049060d5d073f5b6b60d5d073f5b6b5eb9ebc6c0c490c3bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "int.dpool.sina.com.cn" + }, + { + ":path": "/iplookup/iplookup.php" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.sina.com.cn/" + } + ] + }, + { + "seqno": 4, + "wire": "82864189332ba0d50cd4ccb92a04a163b9a429d8100226021032c7075e037ac2e3b7f788011042064410bcdb2bf4ae6fc7539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc690c5c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i3.sinaimg.cn" + }, + { + ":path": "/video/2012/1103/U7805P167DT20121103211853.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.sina.com.cn/" + } + ] + }, + { + "seqno": 5, + "wire": "8286bf049f6273d256040089808402638380683ad905fde200441080411082d38bf4ae6fc8bec690c5c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i3.sinaimg.cn" + }, + { + ":path": "/home/2012/1102/U6041P30DT20121102122146.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.sina.com.cn/" + } + ] + }, + { + "seqno": 6, + "wire": "8286bf04986273d25624290ec08007d8032c818a0f31e29cf495798d2fc8bec690c5c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i3.sinaimg.cn" + }, + { + ":path": "/home/deco/2009/0330/logo_home.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.sina.com.cn/" + } + ] + }, + { + "seqno": 7, + "wire": "8286418a902ba0d50d721e95c95704986113cec50524e3a98100220802e20d50d8a0f31c2bf4ae6fc9bfc790c6c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "d1.sina.com.cn" + }, + { + ":path": "/shh/lechan/20121016sina/logo1.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.sina.com.cn/" + } + ] + }, + { + "seqno": 8, + "wire": "82864189301741aa19a999725504a06273d25604008980840cb1c1e6db0eb6417f7880110420640e32eb2d2fd2b9bfcac0c890c7c3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i0.sinaimg.cn" + }, + { + ":path": "/home/2012/1103/U8551P30DT20121103063734.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.sina.com.cn/" + } + ] + }, + { + "seqno": 9, + "wire": "82864189305741aa19a9997255049f6273d25604008980840163838e34f6b6417f7880110420085a0b4c897e95cdcbc1c990c8c4", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i1.sinaimg.cn" + }, + { + ":path": "/home/2012/1101/U6648P30DT20121101141432.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.sina.com.cn/" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_15.json b/http/http-client/src/test/resources/hpack-test-case/story_15.json new file mode 100644 index 000000000..cade9e4ba --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_15.json @@ -0,0 +1,356 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "8286418748cf18ceb90f4f847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "taobao.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 1, + "wire": "8286418af1e3c2e919e319d721e984c2c1c090bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.taobao.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 2, + "wire": "8286be048d60d5485f314d41e31d0bd73d7fc2c1c090bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.taobao.com" + }, + { + ":path": "/index_global.php" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 3, + "wire": "828641871ae98c9254b92a049362b625ad8100211b03420a94308ac642af31a5c3539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc290c1739c9d29aee30c78f1e1748cf18ceb90f4b06aa42f98a6a0f18e85eb9ebf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "a.tbcdn.cn" + }, + { + ":path": "/p/fp/2011a/assets/space.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.taobao.com/index_global.php" + } + ] + }, + { + "seqno": 4, + "wire": "8286c084c5538e497ca582211f5f2c7cfdf6800b87c490c3bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "a.tbcdn.cn" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.taobao.com/index_global.php" + } + ] + }, + { + "seqno": 5, + "wire": "8286c1048a62b625ad8100219fab1fc6bec490c3bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "a.tbcdn.cn" + }, + { + ":path": "/p/fp/2011hk/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.taobao.com/index_global.php" + } + ] + }, + { + "seqno": 6, + "wire": "8286c184c653032a2f2ac590c4c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "a.tbcdn.cn" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.taobao.com/index_global.php" + } + ] + }, + { + "seqno": 7, + "wire": "8286c2049b62b625ad810020231d10c4b5ad21ac2912b5761e93ad49aa5fa23fc7bec590c4c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "a.tbcdn.cn" + }, + { + ":path": "/p/fp/2010c/js/fp-direct-promo-min.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.taobao.com/index_global.php" + } + ] + }, + { + "seqno": 8, + "wire": "8286418d3533002ba4678c6724952e43d3049c6135a183058de197b7317e1a897f3f073a38cc458200016680bf4ae6c8c2c690c5c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "img01.taobaocdn.com" + }, + { + ":path": "/tps/i1/T1fqY2XilfXXahsVgc-1000-40.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.taobao.com/index_global.php" + } + ] + }, + { + "seqno": 9, + "wire": "8286be049e6135a183058de1b3f4de3f264cbf9f9f9f9f9f9f9f8b0420582cb6bd754dc8c2c690c5c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "img01.taobaocdn.com" + }, + { + ":path": "/tps/i1/T1rZiwXgtfXXXXXXXX-110-135.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.taobao.com/index_global.php" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_16.json b/http/http-client/src/test/resources/hpack-test-case/story_16.json new file mode 100644 index 000000000..af79db78f --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_16.json @@ -0,0 +1,395 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "8286418c2d4bf8375356590c35cf64df847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff60ff3d216a4d83a2a3a4c42c51da4ea54c01fb5094189d5360c9d4d54cb20a8418f5405cbd4ee64370260a5c7cb3ec9463ebb28cb29b3fa7e8dd3e9d7f6a52590c3e46ea65ed416c5e3b49d4a955984be52b8ec4989417094b246327559360c9d4d54d0040ab30a6c193afda9496430f91ba997b505b17349060d0f33d11d07db5fbc9a33f8bbbcd9b0b23ce636fcc5f052fbfb5292c861f237532f6a0b62f1da4ea54aacc25f295c7624c4a0b84a5923193aac7ad263d4881e55985139fc7", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "en.wikipedia.org" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "centralnotice_bucket=1; clicktracking-session=eJko6IiUcEm69ehQfaakQlJfiLy9lShNP; mediaWiki.user.bucket%3Aext.articleFeedback-tracking=10%3Atrack; mediaWiki.user.id=EM83jsjaqPzIMLwBTiKF3aLiiTKeweez; mediaWiki.user.bucket%3Aext.articleFeedback-options=8%3Ashow" + } + ] + }, + { + "seqno": 1, + "wire": "8286c3048b63c1ba998d0335516b1cc5c2c1c090bfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "en.wikipedia.org" + }, + { + ":path": "/wiki/Main_Page" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "centralnotice_bucket=1; clicktracking-session=eJko6IiUcEm69ehQfaakQlJfiLy9lShNP; mediaWiki.user.bucket%3Aext.articleFeedback-tracking=10%3Atrack; mediaWiki.user.id=EM83jsjaqPzIMLwBTiKF3aLiiTKeweez; mediaWiki.user.bucket%3Aext.articleFeedback-options=8%3Ashow" + } + ] + }, + { + "seqno": 2, + "wire": "8286418d8cc942fe0dd4d496430d73d937049360b52fe0dd4d596430d73d933141c722f5cf5fc3538e497ca582211f5f2c7cfdf6800b87c290c1739c9d29aee30c16a5fc1ba9ab2c861ae7b2663c1ba998d0335516b1cc5f6896e4593e94642a6a225410022502edc6c5700d298b46ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "bits.wikimedia.org" + }, + { + ":path": "/en.wikipedia.org/load.php" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://en.wikipedia.org/wiki/Main_Page" + }, + { + "if-modified-since": "Wed, 31 Oct 2012 17:52:04 GMT" + } + ] + }, + { + "seqno": 3, + "wire": "8286c1049360b52fe0dd4d596430d73d933141c722f5cf5fc6c0c490c3bf6896df3dbf4a002a693f75040089403f71966e09d53168df", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "bits.wikimedia.org" + }, + { + ":path": "/en.wikipedia.org/load.php" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://en.wikipedia.org/wiki/Main_Page" + }, + { + "if-modified-since": "Thu, 01 Nov 2012 09:33:27 GMT" + } + ] + }, + { + "seqno": 4, + "wire": "8286c2049360b52fe0dd4d596430d73d933141c722f5cf5fc753032a2f2ac690c5c16896dc34fd280654d27eea0801128115c6d9b82754c5a37f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "bits.wikimedia.org" + }, + { + ":path": "/en.wikipedia.org/load.php" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://en.wikipedia.org/wiki/Main_Page" + }, + { + "if-modified-since": "Sat, 03 Nov 2012 12:53:27 GMT" + } + ] + }, + { + "seqno": 5, + "wire": "8286c4049360b52fe0dd4d596430d73d933141c722f5cf5fc9bfc790c6c2c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "bits.wikimedia.org" + }, + { + ":path": "/en.wikipedia.org/load.php" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://en.wikipedia.org/wiki/Main_Page" + }, + { + "if-modified-since": "Wed, 31 Oct 2012 17:52:04 GMT" + } + ] + }, + { + "seqno": 6, + "wire": "8286c4049360b52fe0dd4d596430d73d933141c722f5cf5fc9bfc790c6c2c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "bits.wikimedia.org" + }, + { + ":path": "/en.wikipedia.org/load.php" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://en.wikipedia.org/wiki/Main_Page" + }, + { + "if-modified-since": "Thu, 01 Nov 2012 09:33:27 GMT" + } + ] + }, + { + "seqno": 7, + "wire": "8286418fb6ba0e3917f06ea6a4b2186b9ec9bf049e63c1ba9ab2c861b05a9823041b198752673583ee388961ebacb22f5d537fca539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc990c8c46896c361be940094d27eea0801128266e34e5c6df53168df699713cf4724629646cad8da95d13a295b7a524607991ba50f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "upload.wikimedia.org" + }, + { + ":path": "/wikipedia/en/c/ca/Kanthirava_cropped.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://en.wikipedia.org/wiki/Main_Page" + }, + { + "if-modified-since": "Fri, 02 Nov 2012 23:46:59 GMT" + }, + { + "if-none-match": "288bdb2fd5e5a4f7272f58fcb083a7e1" + } + ] + }, + { + "seqno": 8, + "wire": "8286c104cf63c1ba9ab2c861b043d349ea43099eda636246241317c7510d54d14c6b28887d07524712a278961ebacb22a27d7e95ccc3a2afcad7c7510d54d14c6b28887d07524712a278961ebacb22a27d7e95cdcdc0cb90cac66896df697e94640a6a225410022502edc65db816d4c5a37f699770af48db924afc6565b69f6a36a47e50146e88b2046c97", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "upload.wikimedia.org" + }, + { + ":path": "/wikipedia/commons/thumb/d/d2/Dancing_girl_ajanta_%28cropped%29.jpg/72px-Dancing_girl_ajanta_%28cropped%29.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://en.wikipedia.org/wiki/Main_Page" + }, + { + "if-modified-since": "Tue, 30 Oct 2012 17:37:15 GMT" + }, + { + "if-none-match": "6e8d56df9be35494b4d9f0ea72ed1a3e" + } + ] + }, + { + "seqno": 9, + "wire": "8286ca049360b52fe0dd4d596430d73d933141c722f5cf5fcfc5cd90ccc8c4", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "bits.wikimedia.org" + }, + { + ":path": "/en.wikipedia.org/load.php" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://en.wikipedia.org/wiki/Main_Page" + }, + { + "if-modified-since": "Sat, 03 Nov 2012 12:53:27 GMT" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_17.json b/http/http-client/src/test/resources/hpack-test-case/story_17.json new file mode 100644 index 000000000..02ee461a6 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_17.json @@ -0,0 +1,368 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "82864188f439ce75c875fa57847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff6092bb03ae7403e30bcf8dc9daf88e067e110023", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yahoo.co.jp" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 1, + "wire": "8286418cf1e3c2fe8739ceb90ebf4aff84c3c2c190c0bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.yahoo.co.jp" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 2, + "wire": "82864187eabfa35332fd2b049960d48e62a1849eb61158982516301609458b0441009b5c8847c4538e497ca582211f5f2c7cfdf6800b87c390c273929d29aee30c78f1e17f439ce75c875fa56c7f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/top/sp2/clr/1/clr-121025.css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 3, + "wire": "8286c0049060d48e62a1849eb6115b141e63af31a5c6539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc590c4bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/top/sp/logo.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 4, + "wire": "8286c104ad60d48e62a188ce7ea849ec2b043d349ea611594861d0c08011300784fc406d88c75545b1879af2f351057e95cdc7bec590c4bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/bookstore/common/special/2012/0829_05/banner/84x84_1.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 5, + "wire": "8286418df5d07e48bfa1ce73ae43afd2bf048260e6c853032a2f2ac790c6c1c5", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yjaxc.yahoo.co.jp" + }, + { + ":path": "/oi" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 6, + "wire": "8286c304a260d48e62a18f051a672d8c4c5a8b60e861360ea4563b0b52624304a0f6c885e634bfc9c0c790c6c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/weather/general/transparent_s/clouds.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 7, + "wire": "8286c304a060d48e62a18f051a672d8c4c5a8b60e861360ea4563b0b52624308b6a5e634bfc9c0c790c6c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/weather/general/transparent_s/sun.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 8, + "wire": "8286c304ad60d48e62a188ce7ea849ec2b043d349ea611594861d0c08011300784fc406d88c75545b1879af2f351097e95cdc9c0c790c6c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/bookstore/common/special/2012/0829_05/banner/84x84_2.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 9, + "wire": "8286c304af60d48e62a18aec2d26b696087a925a928623aac604008986c1e5b03007c4f44849ec2c48b6b2d950d36d83a17e95cdc9c0c790c6c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/premium/contents/bnr/2012/50x50/0928_store_supernatural.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_18.json b/http/http-client/src/test/resources/hpack-test-case/story_18.json new file mode 100644 index 000000000..a6b4b55e3 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_18.json @@ -0,0 +1,371 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "82864187f439ce75c87a7f847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yahoo.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 1, + "wire": "82864188917f46a665c87a7f04b062791824eed45f0861d8bc1eca246021033101d0022a86988b416b9c75262453444179f7893f4582f3ef127a17e95cdfc2539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc190c073939d29aee30c0ed5fd0e739d721e963fcae0b51f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "d.yimg.com" + }, + { + ":path": "/hd/ch7news/7_world/1103_0700_nat_elephant_sml_1898chj-1898chl.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://au.yahoo.com/?p=us" + } + ] + }, + { + "seqno": 2, + "wire": "828641891dabfa1ce73ae43d3f84c5c4c390c26093bb03548aced6b6f3e36c0efc47033f08803dff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "au.yahoo.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "B=4m2rqu589a507&b=3&s=1v" + } + ] + }, + { + "seqno": 3, + "wire": "8286c20488629331ebc0d7e88fc653032a2f2ac590c4c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "d.yimg.com" + }, + { + ":path": "/mi/ywa.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://au.yahoo.com/?p=us" + } + ] + }, + { + "seqno": 4, + "wire": "8286418cf56997f439ce71d6642e43d304856087a633ffc8bfc690c5c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yui.yahooapis.com" + }, + { + ":path": "/combo" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://au.yahoo.com/?p=us" + } + ] + }, + { + "seqno": 5, + "wire": "8286419341496d855876ae6a6cf07b2893c1a42ae43d3f048860931968cd5314ffc9c4c790c6c3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "secure-au.imrworldwide.com" + }, + { + ":path": "/cgi-bin/m" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://au.yahoo.com/?p=us" + } + ] + }, + { + "seqno": 6, + "wire": "8286419024e3b12bca6a87510abfa1ce73ae43d304a960d521365b496a4b015c0c2ade01f9e8760938ec4fdd83aa62c0dc8c1a91cc5fb41bd9600baff97defcac5c890c7c460c8bb03548aced6b6f3e36c0efc47033f08803dfed44150831ea89091d898926a4b00596c2fb4e89d6c2e03ed4eb177320c9803f6a576a278926a4b12123b1300596c2fb4e89f6c2e03", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "chart.finance.yahoo.com" + }, + { + ":path": "/instrument/1.0/%5Eaxjo/chart;range=5d/image;size=179x98" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://au.yahoo.com/?p=us" + }, + { + "cookie": "B=4m2rqu589a507&b=3&s=1v; session_start_time=1351947275160; k_visit=1; push_time_start=1351947295160" + } + ] + }, + { + "seqno": 7, + "wire": "8286bf04a960d521365b496a4b015c0c2ade019ec91824e3b13f760ea98b0372306a47317ed06f65802ebfe5f7bfcbc6c990c8c5be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "chart.finance.yahoo.com" + }, + { + ":path": "/instrument/1.0/%5Eaord/chart;range=5d/image;size=179x98" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://au.yahoo.com/?p=us" + }, + { + "cookie": "B=4m2rqu589a507&b=3&s=1v; session_start_time=1351947275160; k_visit=1; push_time_start=1351947295160" + } + ] + }, + { + "seqno": 8, + "wire": "8286bf04a960d521365b496a4b015c0c0ed92d44907960938ec4fdd83aa62c0dc8c1a91cc5fb41bd9600baff97decbc6c990c8c5be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "chart.finance.yahoo.com" + }, + { + ":path": "/instrument/1.0/audusd=x/chart;range=5d/image;size=179x98" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://au.yahoo.com/?p=us" + }, + { + "cookie": "B=4m2rqu589a507&b=3&s=1v; session_start_time=1351947275160; k_visit=1; push_time_start=1351947295160" + } + ] + }, + { + "seqno": 9, + "wire": "82864191252b8ed5fd0e739d73f72d89b6c2ae43d3048a63a2229681a620c4063fccc3ca90c9c6", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "cm.au.yahoo.overture.com" + }, + { + ":path": "/js_flat_1_0/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://au.yahoo.com/?p=us" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_19.json b/http/http-client/src/test/resources/hpack-test-case/story_19.json new file mode 100644 index 000000000..e1fe53006 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_19.json @@ -0,0 +1,365 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "82864187f43aa42f95ecb7847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yandex.ru" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 1, + "wire": "8286418bf1e3c2fe875485f2bd96ff84c2c1c090bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.yandex.ru" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 2, + "wire": "8286418bf438d0bfa1d5217caf65bf04d06087b6a4b1c6af1133ef08a4eba9a00002fd9e87b3621b79b5e61129d72e82f3af50bde207784baad84b2fee48663c22cd09452ebd5ab5bee7aecd4630cb7f362d97833d17f8976697b14bc6f85d2bbfc3539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc290c173909d29aee30c78f1e17f43aa42f95ecb5860994c15fda9e875485f369a481c682069b65d742cb617da7da6c3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yabs.yandex.ru" + }, + { + ":path": "/count/Vnw_3zF2dkO40002Zhl8KGa5KPK2cmPfMeYpO2zG0vAeOuAefZIAgoA2KAe2fPOOP96yq4ba1fDKGQC1hlDVeQN8GfVD17e7" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yandex.ru/" + }, + { + "cookie": "t=p; yandexuid=6410453771351949451" + } + ] + }, + { + "seqno": 3, + "wire": "8286c104ce6087b6a4b1c6af11334ca97bc766800003f67a1ecd886de6d6e7aecd4630cb7f34f45fe25d9a5ec52f1be1746cc1d89aaa69fcc2253ae5d048f60e6fcfde53738663c22cd0e8d0e399097dde4cffc6c0c490c3bfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yabs.yandex.ru" + }, + { + ":path": "/count/Vnw_3mft8wq40000Zhl8KGa5KP6yq4ba1fDKhlDVeQN8GfVD17a3=qcOn49K2cmPfMcbQagXZWgYAgoA2KAMM66IcD7W3" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yandex.ru/" + }, + { + "cookie": "t=p; yandexuid=6410453771351949451" + } + ] + }, + { + "seqno": 4, + "wire": "82864187f43aa42f95d09f049d6282cc762262bbf6bfab943b335d020595fc87e99ab36e8b04e75cc43fc7c6c590c4", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yandex.st" + }, + { + ":path": "/lego/_/pDu9OWAQKB0s2J9IojKpiS_Eho.ico" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 5, + "wire": "8286be049663c78f0c05765b7d8f1e3c30663d0ea90be595ebaa6fc7c1c590c4c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yandex.st" + }, + { + ":path": "/www/1.359/www/i/yandex3.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yandex.ru/" + } + ] + }, + { + "seqno": 6, + "wire": "8286be04906293d920d6a0f31d833141e63af5d537c7c1c590c4c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yandex.st" + }, + { + ":path": "/morda-logo/i/logo.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yandex.ru/" + } + ] + }, + { + "seqno": 7, + "wire": "82864189a48bfa1d5217caf65b048a63c0d249d874426da6ffc8c2c690c5c1c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "mc.yandex.ru" + }, + { + ":path": "/watch/722545" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yandex.ru/" + }, + { + "cookie": "t=p; yandexuid=6410453771351949451" + } + ] + }, + { + "seqno": 8, + "wire": "8286bf04a563c78f0c05765b7d8f1e3c3158e62a1690a8ea93d6c78f1e162210c45e3c78588842e4423fc8538e497ca582211f5f2c7cfdf6800b87c790c6c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yandex.st" + }, + { + ":path": "/www/1.359/www/pages-desktop/www-css/_www-css.css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yandex.ru/" + } + ] + }, + { + "seqno": 9, + "wire": "8286c0049e63c78f0c44c4563b5d6b46b4f98f7e39bd62e7e8192eb3e28eb51d7aea9bc9c3c790c6c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yandex.st" + }, + { + ":path": "/www/_/_r7pp-b-hKoDbgyGYy0IB3wlkno.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yandex.ru/" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_20.json b/http/http-client/src/test/resources/hpack-test-case/story_20.json new file mode 100644 index 000000000..0d7114ac6 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_20.json @@ -0,0 +1,6002 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "82864188f439ce75c875fa57847abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c153b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ff6092bb03ae7403e30bcf8dc9daf88e067e110023", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yahoo.co.jp" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 1, + "wire": "8286418cf1e3c2fe8739ceb90ebf4aff84c3c2c190c0bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.yahoo.co.jp" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 2, + "wire": "82864187eabfa35332fd2b049960d48e62a1849eb61158982516301609458b0441009b5c8847c4538e497ca582211f5f2c7cfdf6800b87c390c273929d29aee30c78f1e17f439ce75c875fa56c7f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/top/sp2/clr/1/clr-121025.css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 3, + "wire": "8286c0049060d48e62a1849eb6115b141e63af31a5c6539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc590c4bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/top/sp/logo.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 4, + "wire": "8286c104ad60d48e62a188ce7ea849ec2b043d349ea611594861d0c08011300784fc406d88c75545b1879af2f351057e95cdc7bec590c4bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/bookstore/common/special/2012/0829_05/banner/84x84_1.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 5, + "wire": "8286418df5d07e48bfa1ce73ae43afd2bf048260e6c853032a2f2ac790c6c1c5", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yjaxc.yahoo.co.jp" + }, + { + ":path": "/oi" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 6, + "wire": "8286c304a260d48e62a18f051a672d8c4c5a8b60e861360ea4563b0b52624304a0f6c885e634bfc9c0c790c6c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/weather/general/transparent_s/clouds.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 7, + "wire": "8286c304a060d48e62a18f051a672d8c4c5a8b60e861360ea4563b0b52624308b6a5e634bfc9c0c790c6c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/weather/general/transparent_s/sun.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 8, + "wire": "8286c304ad60d48e62a188ce7ea849ec2b043d349ea611594861d0c08011300784fc406d88c75545b1879af2f351097e95cdc9c0c790c6c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/bookstore/common/special/2012/0829_05/banner/84x84_2.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 9, + "wire": "8286c304af60d48e62a18aec2d26b696087a925a928623aac604008986c1e5b03007c4f44849ec2c48b6b2d950d36d83a17e95cdc9c0c790c6c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/premium/contents/bnr/2012/50x50/0928_store_supernatural.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 10, + "wire": "8286c304ad60d48e62a188ce7ea849ec2b043d349ea611594861d0c08011300784fc406d88c75545b1879af2f35132bf4ae6c9c0c790c6c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/bookstore/common/special/2012/0829_05/banner/84x84_3.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 11, + "wire": "8286c304ad60d48e62a188ce7ea849ec2b043d349ea611594861d0c08011300784fc406d88c75545b1879af2f35136bf4ae6c9c0c790c6c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/bookstore/common/special/2012/0829_05/banner/84x84_5.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 12, + "wire": "828641881997f46a665fa57f04a862393bb0d80006c4c040f01e7da60400882013ec525b68f6dd9d6aa4f1a7a4bda9f5ede586bcc697cac1c890c7c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/500052/1080894/20121029/meulz5rknmobtjfqmyz8-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 13, + "wire": "8286c0048260e6cabfc890c7c2c6", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yjaxc.yahoo.co.jp" + }, + { + ":path": "/oi" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 14, + "wire": "8286be04a762393bb0e81b038c040f08407d8100220804d312cb4f837893d46797c7a44a9f350c2b0d798d2fcac1c890c7c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/70506/1082209/20121024/ffmwiwdybofwysftxna1-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 15, + "wire": "8286be049d62393bb1e8739cec741f71a0961ab4b1ea51c5dcc8b474371263ad7e88cabfc890c7c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/yahoo/javascript/yfa_visual5_tbp.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 16, + "wire": "8286c5049963a0fb8d04b0d5a5896b8a31a0b14724530e26d702ed097e88cabfc890c7c2c6", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.yahoo.co.jp" + }, + { + ":path": "/javascript/fp_base_bd_ga_5.0.42.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 17, + "wire": "8286be04a262393bb1e8739cec741f71a0961ab4b0441181000e01e134c5068c478e58a3717e88cabfc890c7c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/yahoo/javascript/csc/20060824/lib2obf_b6.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 18, + "wire": "8286c4049960d48e62a1849eb6115898aec6131c51ccb04400840bd754dfcac1c890c7c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/top/sp2/pr/tb_bg-120110.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 19, + "wire": "8286c4049e60d48e62a1849eb6115898b679189cf496b1cc58a399608801132bd754dfcac1c890c7c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/top/sp2/uhd/homepage_bg-120123.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 20, + "wire": "8286c4049560d48e62a1841887a90c4673f5424f6142e2f31a5fcac1c890c7c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/sicons/bookstore16.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 21, + "wire": "8286c4049260d48e62a1841887a90c527ee6285c5e634bcac1c890c7c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/sicons/movie16.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 22, + "wire": "8286c4049260d48e62a1841887a90c4c3a4a171798d2ffcac1c890c7c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/sicons/game16.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 23, + "wire": "8286c4049b60d48e62a1849eb6115898253531598910e8a1608820034bd754dfcac1c890c7c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/top/sp2/cmn/pic_all-121004.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 24, + "wire": "8286c4049460d48e62a1841887a90c4a7b136d450b8bcc697fcac1c890c7c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/sicons/fortune16.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 25, + "wire": "8286c4049a60d48e62a1849eb61158982d333121903424b64494d025ebaa6fcac1c890c7c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/top/sp2/emg/disaster_ttl2.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 26, + "wire": "8286c4049c60d48e62a18ee690a75927acc44316148c04410b006622802bf4ae6fcac1c890c7c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/video-topics/rec/1211/03_e01.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 27, + "wire": "8286c4049960d48e62a1849eb61158982516301609458b0441009b5ebaa6cac1c890c773a59d29aee30c755fd1a9997e95b06a473150c24f5b08ac4c128b180b04a2c58220804dae4423", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/top/sp2/clr/1/clr-121025.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://k.yimg.jp/images/top/sp2/clr/1/clr-121025.css" + } + ] + }, + { + "seqno": 28, + "wire": "8286c5049b60d48e62a1849eb61158982535b043d35c43a285822080225ebaa6cbc2c990c8c3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/top/sp2/cmp/comp_all-121012.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 29, + "wire": "8286c5049c60d48e62a1849eb611589845674d069a74b020042c040c84ebf4ae6fcbc2c990c8c3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "k.yimg.jp" + }, + { + ":path": "/images/top/sp2/spotlight/2011/1031o.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 30, + "wire": "8286418ba8be10b917f46a665fa57f04a360d48e62a1849eb3110c08011042065600000005f6564573ac000164cf6d31afd2b9bfccc3ca90c9c4", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "news.c.yimg.jp" + }, + { + ":path": "/images/topics/20121103-00000193-sph-000-thumb.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 31, + "wire": "8286c004a662393bb02132eb0103c0659030200441081962399c41dd447313b16a23f5fa73cf5586bf4ae6ccc3ca90c9c4", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/2237/1080330/20121103/bg6so7sbgcqenc9py6xk-a.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + } + ] + }, + { + "seqno": 32, + "wire": "8286c704896251f7310f52e621ffcccbca90c9c8", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.yahoo.co.jp" + }, + { + ":path": "/favicon.ico" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 33, + "wire": "8286c0049660d48e62a1841496d864fa62b958f5d10525b6157e88ccc1ca90c973ae9d29aee30c483351eaa2f842fe8739ceb90ebf4ad8948c22b3d8943151abacf54482d862a18ff02cb617d965a7da", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/images/security/pf/yjsecure.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 34, + "wire": "8286c3048a63a218f5d07e48bf447fcdc2cb90cabec9", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yjaxc.yahoo.co.jp" + }, + { + ":path": "/js/yjaxc.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 35, + "wire": "828641909066a3d545f085fd0e739d721d7e95ff04926252308acf6250c546aeb3d5120b618a863fcecdcc90cbc660e3bb03ae7403e30bcf8dc9daf88e067e110023fb539e5dfab5e4bdbb0dddb821bf049074b77da867465921725875e6d9533a32fa3f2efd778f9b9905b6a9b59b8e6c0cddd1dde870fe2f79adfa2605a9f1a22b7f268919aa77d0bd5fe3873605be3bc01f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "dailynews.yahoo.co.jp" + }, + { + ":path": "/fc/sports/nippon_series/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://www.yahoo.co.jp/" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b; YJTOPICSFBREAD=d=juTus3MJdA6fAPKQn3MJyoWvkTaY6I2RngPiVKE3BMv8AFX.C4TMg0utwM_uXg_sKn7y2yDVFKE-&v=1" + } + ] + }, + { + "seqno": 36, + "wire": "8286bf048b625231d10c4b1c55917e88cfc4cd90ccc0be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "dailynews.yahoo.co.jp" + }, + { + ":path": "/fc/js/fb_pc.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b; YJTOPICSFBREAD=d=juTus3MJdA6fAPKQn3MJyoWvkTaY6I2RngPiVKE3BMv8AFX.C4TMg0utwM_uXg_sKn7y2yDVFKE-&v=1" + } + ] + }, + { + "seqno": 37, + "wire": "8286c304a862393bb0e85c13ec040eb2e05e60400882013ec7aafc93d7a2f524565b3faae4323b5ab0d7e95cdfcfc6cd90ccc0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/71629/1073618/20121029/ypxcyyekc_ruhypdisqu-a.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 38, + "wire": "8286418732fe8d4ccbf4af048e60d48e62a18a8be10c4a45e634bfd0c7ce90cdc1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/news/fc.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 39, + "wire": "8286be049060d48e62a18310f5315ce749d798d2ffd0c7ce90cdc1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/icon/photo.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 40, + "wire": "8286be048e60d48e62a18a6762a2f842f31a5fd0c7ce90cdc1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/mh/news.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 41, + "wire": "8286c404a562393bb0c818081d744d098100220804fb06f3199145affa989efcfb93acb5569586bf4ae6d0c7ce90cdc1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/30/1077242/20121029/ixbislu9ygczxzdkfnpt-a.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 42, + "wire": "8286be04a160d48e62a18a8be10c4a3216339fab1517c222c23216339fac4eb9e5d717aea9bfd0c7ce90cdc1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/news/facebook/news_Facebook_76x76.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 43, + "wire": "8286be049360d48e62a18b0759a4602bb6b818b684afd11fd0c5ce90cdc1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/rapid/1.5.0/ult.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 44, + "wire": "8286be048c60d48e62a18a8be04bcc697fd0c7ce90cdc1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/new2.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 45, + "wire": "8286be049d60d48e62a1849eb3110c78375331515093d662222310f544d017aea9bfd0c7ce90cdc1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/topics/wiki/nestopics_icon_40.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 46, + "wire": "8286c4049b62393bb1e8739cec741f71a0961ab4b1ea51c5dcc8b47436bf447fd0c5ce90cdc1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/yahoo/javascript/yfa_visual5.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 47, + "wire": "8286c404a662393bb017d96020744213ac0801104027d8b7d7bf1d6b2f9e88f7e8c2f73112d56b0d798d2fd0c7ce90cdc1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/193/1072227/20121029/uyzwkpexjszyi2zgct4p-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 48, + "wire": "8286c404a662393bb027db7d8081e6c2275810022084026241d1dfbbf5bf2f87d361a31f81f82ac35e634bd0c7ce90cdc1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/2959/1085127/20121102/dalvv9p9fw9tribawawe-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 49, + "wire": "8286c404a762393bb027db7d8081e6c226981002208402623f6fd9ee6aac2d3ea41f98eb68d3c6b0d798d2ffd0c7ce90cdc1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/2959/1085124/20121102/bz9rzgnremydaxbp4ihb-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 50, + "wire": "8286be04a460d48e62a1849eb3110c78375330590c93d8c24f598888abb22a0d5753533454097aea9bd0c7ce90cdc1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/topics/wiki/editor/topics_pr_linkimg_l2.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 51, + "wire": "8286418aa2b4ae45fd1a9997e95f04c060d4c4834d336cbb4e46417f73f3f22fe97157ca875bd8b2cb791000b7a0be05bb3e06074c8c08011042065600000036d09640ea4567580002ddcc5f0bf4ae6fd1c8cf90cec2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "lpt.c.yimg.jp" + }, + { + ":path": "/im_sigg537mI30DS9hWeZeGpWl75Q---x200-y190-q90/amd/20121103-00000542-sanspo-000-view.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 52, + "wire": "8286418a1d322e45fd1a9997e95f04cc60d4c4834d363b68c1d33f89bdebf5671bfd7f5f3e9d754cb2cb791000b7a0b2cadd9f0303a64604008820642b0000036f09e5bd748887b2b4d85aa4580002cd85c740d002b77317c2fd2b9bd2c9d090cfc3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "amd.c.yimg.jp" + }, + { + ":path": "/im_siggHulEjLwgzPyrVDkZ9oNPng---x200-y133-q90/amd/20121031-00005828-yj_corptrend-000-51670401-view.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 53, + "wire": "8286be04cc60d4c4834d359a2fe767f6babb55e3435fa1c3cfbcff82d8b2cb791000b7a0b2cadd9f0303a646040088210056000006de640b7ae9110f6569b0b548b000059b0bad85a0016ee62f85fa5737d2c9d090cfc3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "amd.c.yimg.jp" + }, + { + ":path": "/im_siggrMDL3ZpnqnwM4Z1FYvhX2Q---x200-y133-q90/amd/20121101-00005830-yj_corptrend-000-51751400-view.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 54, + "wire": "8286c0049860d48e62a1849eb3110c110860d4d67b131772d825c8847fd2cbd090cfc3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/topics/css/import_ver2.css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 55, + "wire": "8286c004a960d48e62a1821e9a4b610ac7443141a3431d3b5a5b3d3043d85602bb4b898e9dad2d9e97a4d52fd11fd2c7d090cfc3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/commerce/js/libs/jquery/core/1.4.2/jquery.min.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 56, + "wire": "8286c6049f63d5a663a56c5b3c8c1e8f54d6623015c0b8983533316cf25e9eaeabd754dfd2c9d090cfc3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/yui/jp/uhd/olympic/1.0.2/img/uhdChnk.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 57, + "wire": "8286c004a060d48e62a18a8be10c770b1eaa8a6a87dcd122bb0c92c42004407c4e2f5d537fd2c9d090cf73ad9d29aee30c197f46a665fa56c1a91cc543093d6622182210c1a9acf6262ee5b04b9108ff241a4b00801104027f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/news/v1/yn_gnavi_sprite_20120926.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/topics/css/import_ver2.css?date=20121029" + } + ] + }, + { + "seqno": 58, + "wire": "8286c9048260e6d3c8d190d0c4cf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yjaxc.yahoo.co.jp" + }, + { + ":path": "/oi" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 59, + "wire": "8286c9048260e6d3c8d190d0c4cf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yjaxc.yahoo.co.jp" + }, + { + ":path": "/oi" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 60, + "wire": "8286c1049660d48e62a1849eb3110c20e430e86234d5a3caf5d537d3cad190d0be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/topics/social/btnMx.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/topics/css/import_ver2.css?date=20121029" + } + ] + }, + { + "seqno": 61, + "wire": "8286c1049f60d48e62a1849eb3110c78375331e927acc44448aec324b11887a90bd754dfd3cad190d0be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/topics/wiki/ytopics_sprite_icons.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/topics/css/import_ver2.css?date=20121029" + } + ] + }, + { + "seqno": 62, + "wire": "8286c104a360d48e62a1849eb3110c78375331e927acc44448aec324b14632759ac3db54885ebaa6d3cad190d0be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/topics/wiki/ytopics_sprite_backgrounds.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/topics/css/import_ver2.css?date=20121029" + } + ] + }, + { + "seqno": 63, + "wire": "8286c1049860d48e62a1849eb3110c783753316168de38f39654af31a5d3cad190d0be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/topics/wiki/relTabLeft.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/topics/css/import_ver2.css?date=20121029" + } + ] + }, + { + "seqno": 64, + "wire": "8286c1049960d48e62a1849eb3110c783753316168de38f69a69d2bcc697d3cad190d0be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/topics/wiki/relTabRight.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/topics/css/import_ver2.css?date=20121029" + } + ] + }, + { + "seqno": 65, + "wire": "8286c1049960d48e62a1849eb3110c783753311db45054c5419095e634bfd3cad190d0be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/topics/wiki/bullet_list.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/topics/css/import_ver2.css?date=20121029" + } + ] + }, + { + "seqno": 66, + "wire": "8286c7049963d5a663a56c5b2a580ae05c0c1a9998b532de9eaeabd754dfd3cad190d0c4", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/yui/jp/uft/1.0.0/img/utfChnk.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 67, + "wire": "8286c1049b60d48e62a1849eb3110c783753303210f6d49de64d05bb32f5d537d3cad190d0be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/topics/wiki/accountTitleBg.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/topics/css/import_ver2.css?date=20121029" + } + ] + }, + { + "seqno": 68, + "wire": "8286c1049c60d48e62a1849eb3110c20e430e86115d864962310fbaa405c5ebaa6d3cad190d0be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/topics/social/sprite_icoSns16.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/topics/css/import_ver2.css?date=20121029" + } + ] + }, + { + "seqno": 69, + "wire": "8286c1049a60d48e62a1849eb3110c783753309b0b549bcc9a0b7665ebaa6fd3cad190d0be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/topics/wiki/trendTitleBg.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/topics/css/import_ver2.css?date=20121029" + } + ] + }, + { + "seqno": 70, + "wire": "8286c704a262393bb1e8739cec741f71a0961ab4b0441181000e01e134c5068c478e58a3697e88d3c8d190d0c4", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/yahoo/javascript/csc/20060824/lib2obf_b4.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 71, + "wire": "828641881cebfa35332fd2bf04a862393bb0171a65b698081e680eb6c0801104200b0d4a51a25efeda7488f243fa928ef42c35fa5737d4cbd290d1c5", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ah.yimg.jp" + }, + { + ":path": "/bdv/164354/1084075/20121101/4feasfvz47csxcoydlvl-a.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 72, + "wire": "8286418eae81a653d94ae9f064a4b62e43d3048863c1a498a942fd11d5cad390d2c660ff4cacd241dd9b8165b0bed3ac81c69d75c71a642e080e01b6bed4eb0040bb2dae1005708995c2cb617da75b65c089f7de7fed49ad2a1311a483b8556610b2d85f69d6d971b79a7c2dbacfda91456a691c0d32f32f32e3cb882d0190b6d81b5c2cb617da75b684b8596c2fb4eb6d0970b2d85f69d6da12e1fb5228ad4d31c0d32f32f32e3cb89708170b2d85f69d6da17da91456a69f7034cbccbccb8f2e165b0bed3adb425c2b857b534911641fd486b0a44ff7ff2d4d2425507f521ac2913fdffcb534929920feaa3d45feff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "platform.twitter.com" + }, + { + ":path": "/widgets.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + }, + { + "cookie": "pid=v3:1351947306477664316206054; k=10.35.101.123.1351947536129989; guest_id=v1%3A135194753658491573; __utma=43838368.2140315505.1351947542.1351947542.1351947542.1; __utmb=43838368.2.10.1351947542; __utmz=43838368.1351947542.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)" + } + ] + }, + { + "seqno": 73, + "wire": "8286bf049b63c1a498a94309f052a628ed4a4f52e165b0bcd3cf3825e74d347fd6d5d490d3c7be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "platform.twitter.com" + }, + { + ":path": "/widgets/tweet_button.1351848862.html" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + }, + { + "cookie": "pid=v3:1351947306477664316206054; k=10.35.101.123.1351947536129989; guest_id=v1%3A135194753658491573; __utma=43838368.2140315505.1351947542.1351947542.1351947542.1; __utmb=43838368.2.10.1351947542; __utmz=43838368.1351947542.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)" + } + ] + }, + { + "seqno": 74, + "wire": "8286418e21eaa8a44af28c858ce7eabd454f048a63a0e2cbad81d142fd11d7ccd590d4c8", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "connect.facebook.net" + }, + { + ":path": "/ja_JP/all.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + } + ] + }, + { + "seqno": 75, + "wire": "828641958faa3a8eb32f20cd47aa8be10bfa1ce73ae43afd2b04856242a466a3d8cdd690d5c9c7", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "bkskapi.dailynews.yahoo.co.jp" + }, + { + ":path": "/detail" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b; YJTOPICSFBREAD=d=juTus3MJdA6fAPKQn3MJyoWvkTaY6I2RngPiVKE3BMv8AFX.C4TMg0utwM_uXg_sKn7y2yDVFKE-&v=1" + } + ] + }, + { + "seqno": 76, + "wire": "8286c1049c63c1a498a943129e8a0fe228ed4a4f52e165b0bcd3cf3825e74d347fd8d7d690d5c9c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "platform.twitter.com" + }, + { + ":path": "/widgets/follow_button.1351848862.html" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + }, + { + "cookie": "pid=v3:1351947306477664316206054; k=10.35.101.123.1351947536129989; guest_id=v1%3A135194753658491573; __utma=43838368.2140315505.1351947542.1351947542.1351947542.1; __utmb=43838368.2.10.1351947542; __utmz=43838368.1351947542.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)" + } + ] + }, + { + "seqno": 77, + "wire": "82864189ad74f832525b1721e90485612bcc697fd9d0d790d673ae9d29aee30c5740d329eca574f832525b1721e963c1a498a94309f052a628ed4a4f52e165b0bcd3cf3825e74d347fc2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "p.twitter.com" + }, + { + ":path": "/t.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://platform.twitter.com/widgets/tweet_button.1351848862.html" + }, + { + "cookie": "pid=v3:1351947306477664316206054; k=10.35.101.123.1351947536129989; guest_id=v1%3A135194753658491573; __utma=43838368.2140315505.1351947542.1351947542.1351947542.1; __utmb=43838368.2.10.1351947542; __utmz=43838368.1351947542.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)" + } + ] + }, + { + "seqno": 78, + "wire": "8286418e24952e3accba7c19292d8b90f4ff048d602c5b65086087b6a4afd107abdbd0d990d8bfc3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "cdn.api.twitter.com" + }, + { + ":path": "/1/urls/count.json" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://platform.twitter.com/widgets/tweet_button.1351848862.html" + }, + { + "cookie": "pid=v3:1351947306477664316206054; k=10.35.101.123.1351947536129989; guest_id=v1%3A135194753658491573; __utma=43838368.2140315505.1351947542.1351947542.1351947542.1; __utmb=43838368.2.10.1351947542; __utmz=43838368.1351947542.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)" + } + ] + }, + { + "seqno": 79, + "wire": "82864188b174f835332e43d3048363a1d3dcd3da90d9c0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "r.twimg.com" + }, + { + ":path": "/jot" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://platform.twitter.com/widgets/tweet_button.1351848862.html" + } + ] + }, + { + "seqno": 80, + "wire": "8286bf048d602c5a82d8861139fc2fd107abdcd1da90d973af9d29aee30c5740d329eca574f832525b1721e963c1a498a943129e8a0fe228ed4a4f52e165b0bcd3cf3825e74d347fc5", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "cdn.api.twitter.com" + }, + { + ":path": "/1/users/show.json" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://platform.twitter.com/widgets/follow_button.1351848862.html" + }, + { + "cookie": "pid=v3:1351947306477664316206054; k=10.35.101.123.1351947536129989; guest_id=v1%3A135194753658491573; __utma=43838368.2140315505.1351947542.1351947542.1351947542.1; __utmb=43838368.2.10.1351947542; __utmz=43838368.1351947542.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)" + } + ] + }, + { + "seqno": 81, + "wire": "8286c204856255e634bfddd4db90dabec5", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "p.twitter.com" + }, + { + ":path": "/f.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://platform.twitter.com/widgets/follow_button.1351848862.html" + }, + { + "cookie": "pid=v3:1351947306477664316206054; k=10.35.101.123.1351947536129989; guest_id=v1%3A135194753658491573; __utma=43838368.2140315505.1351947542.1351947542.1351947542.1; __utmb=43838368.2.10.1351947542; __utmz=43838368.1351947542.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)" + } + ] + }, + { + "seqno": 82, + "wire": "8286bf048363a1d3ddd4db90dabe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "r.twimg.com" + }, + { + ":path": "/jot" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://platform.twitter.com/widgets/follow_button.1351848862.html" + } + ] + }, + { + "seqno": 83, + "wire": "8386418c39115afdcb619069aa5c87a784dedddc90db0f0d82085b5f911d75d0620d263d4c1c88ad6b0bdad2a13f", + "headers": [ + { + ":method": "POST" + }, + { + ":scheme": "http" + }, + { + ":authority": "ocsp.verisign.com" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "115" + }, + { + "content-type": "application/ocsp-request" + } + ] + }, + { + "seqno": 84, + "wire": "8286cf04896251f7310f52e621ffdfd6dd90dcce", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "dailynews.yahoo.co.jp" + }, + { + ":path": "/favicon.ico" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b; YJTOPICSFBREAD=d=juTus3MJdA6fAPKQn3MJyoWvkTaY6I2RngPiVKE3BMv8AFX.C4TMg0utwM_uXg_sKn7y2yDVFKE-&v=1" + } + ] + }, + { + "seqno": 85, + "wire": "8286cd048e60d48e62a182210c7ae825c8847fdfd8dd90dc73ac9d29aee30c4e51c941aa2a17f439ce75c875fa56c4f47f83804008821032b0000001b684b207522b3ad18d05", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/css/yj2.css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 86, + "wire": "8286ce048c60d48e62a1825051d8bcc697e0d7de90ddbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/clear.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 87, + "wire": "8286ce049860d48e62a18a8be10c10f1d83aa435533081d48acebcc697e0d7de90ddbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/news/cobranding/sanspo.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 88, + "wire": "8286ce0497628346c545f0863a20f531d116444a0684441882bf447fe0d5de90ddbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/lib/news/json/jsr_class_1_1.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 89, + "wire": "8286418f9ca3928354542fe8739ceb90ebf4af04032f686ce1e0df90ded2dd", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "headlines.yahoo.co.jp" + }, + { + ":path": "/hl" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://dailynews.yahoo.co.jp/fc/sports/nippon_series/?1351933494" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 90, + "wire": "8286cf04a2628346c545f08610721874683c96d0562c28e849a92ee28ec24f106202d49aa5fa23e1d6df90debf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/lib/news/socialModule/realtimeSearch_1_0-min.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 91, + "wire": "8286cf049a628346c545f0863a76b4b67a63a76b4b67a5d25a6ba0692afd11e1d6df90debf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/lib/news/jquery/jquery.template.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 92, + "wire": "8286cf04a0628346c545f08610721874683c96d05625190b19cfd620c4cc415a9354bf447fe1d6df90debf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/lib/news/socialModule/facebook_1_3_1-min.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 93, + "wire": "8286cd04c260d4c4834d377d3562682ec5f9fb970b7bd1975ceee1c3b16596f220016f417c0b767c0c0e9918100220840cac0000006da12c81d48aceb000059a5bb98be17e95cde1d8df90debf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "amd.c.yimg.jp" + }, + { + ":path": "/im_siggvNnG417_XZJF5TsJPh7FFQ---x200-y190-q90/amd/20121103-00000542-sanspo-000-4-view.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 94, + "wire": "8286d504a762393bb0e81b038c040f0841030200441009a6012ca4eed4964ef1ac03bd3bd87e96ac35e634bfe1d8df90debf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/70506/1082210/20121024/0ffcv4drh8ir07jvroju-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 95, + "wire": "8286d504a762393bb0e85c13ec040f082f3ec0801104027d815414c9ecc491ee8e94e994be6332c35fa5737fe1d8df90debf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/71629/1082189/20121029/2n1tdzicd8j7eotfexbi-a.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 96, + "wire": "8286cf0494628346c545f0863a76b4b67a63a76b4b67a5fa23e1d6df90debf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/lib/news/jquery/jquery.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 97, + "wire": "8286cf0497628346c545f0863c1a498a9431e0d24c54a220c415fa23e1d6df90debf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/lib/news/widgets/widgets_1_1.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 98, + "wire": "8286d504a662393bb027db7d8081e6c22758100220840260d40fa0a2922f6789f8fa4c6abb27d2c35e634be1d8df90debf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/2959/1085127/20121102/ilaj2_d_zo_9bjginqty-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 99, + "wire": "8286d504a662393bb027db7d8081e6c22698100220840263bf71d111273a1917589d3f5313abeb0d798d2fe1d8df90debf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/2959/1085124/20121102/vval_chos32k_7okick9-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 100, + "wire": "8286d504a562393bb0c818081d1059698100220804fb1dbb8891513f48ec183b58dd686dbf0b0d7e95cde1d8df90debf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/30/1072134/20121029/qv2c_lhjbra0qr5ps55w-a.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 101, + "wire": "8286cf049960d48e62a18a8be10c770b044218a468496c5aa2f842e4423fe1dadf90debf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/news/v1/css/master-news.css" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/css,*/*;q=0.1" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 102, + "wire": "8286d7048260e6e1d6df90debfdd", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yjaxc.yahoo.co.jp" + }, + { + ":path": "/oi" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 103, + "wire": "8286cf049a60d48e62a18a8be10c770b1517c22241c861d11da949ea5e634be1d8df90de73a89d29aee30c197f46a665fa56c1a91cc5431517c218ee1608843148d092d8b545f085c8847f9dc21f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/news/v1/news_socialbutton.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/news/v1/css/master-news.css?v11" + } + ] + }, + { + "seqno": 104, + "wire": "8286d0049c60d48e62a18a4b2186c7aa6d3306a666283545e4690b135e42bcc697e2d9e090dfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/media/ymui/img/lineWide_4x1.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/news/v1/css/master-news.css?v11" + } + ] + }, + { + "seqno": 105, + "wire": "8286d0049960d48e62a18a8be10c770b1eaa8915d864962310f5217aea9be2d9e090dfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/news/v1/yn_sprite_icons.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/news/v1/css/master-news.css?v11" + } + ] + }, + { + "seqno": 106, + "wire": "8286d0049a60d48e62a18a4b2186c7aa6d3306a666083b2cb0e9884bd754dfe2d9e090dfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/media/ymui/img/carrrot_2.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/news/v1/css/master-news.css?v11" + } + ] + }, + { + "seqno": 107, + "wire": "8286d0049e60d48e62a18a4b2186c7aa6d3306a6662b9ce93e92f889a6fc85b5e634bfe2d9e090dfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/media/ymui/img/photoNew_45x15.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/news/v1/css/master-news.css?v11" + } + ] + }, + { + "seqno": 108, + "wire": "8286d0049d60d48e62a18a8be10c770b08aec324b14736ddfb8a3b093dd3f95ebaa6e2d9e090dfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/news/v1/sprite_bgRTSearchBox.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/news/v1/css/master-news.css?v11" + } + ] + }, + { + "seqno": 109, + "wire": "8286d0049a60d48e62a18a8be10c770b08aec324b11887dfe0c9496c5ebaa6e2d9e090dfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/news/v1/sprite_icoTwitter.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/news/v1/css/master-news.css?v11" + } + ] + }, + { + "seqno": 110, + "wire": "8286d0049d60d48e62a18a8be10c770b1eaa8915d8649628c64eb3587b6a917aea9be2d9e090dfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/news/v1/yn_sprite_background.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/news/v1/css/master-news.css?v11" + } + ] + }, + { + "seqno": 111, + "wire": "8286d0049460d48e62a18a8be10c770b160eaea6aa65ebaa6fe2d9e090dfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/news/v1/ranking.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/news/v1/css/master-news.css?v11" + } + ] + }, + { + "seqno": 112, + "wire": "8286d0049a60d48e62a18a8be10c770b1eaa8a6a87dcd122bb0c92af5d537fe2d9e090dfbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/news/v1/yn_gnavi_sprite.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/news/v1/css/master-news.css?v11" + } + ] + }, + { + "seqno": 113, + "wire": "8286d8048260e6e2d7e090dfc0de", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yjaxc.yahoo.co.jp" + }, + { + ":path": "/oi" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 114, + "wire": "8286d0049063d5a663a56c5b42581d961fc2f31a5fe2d9e090dfc0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/yui/jp/ult/arrow.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 115, + "wire": "8286d004a060d48e62a18a0c849aa99849cf431eba0fc918f5d07e48b1a5b0749579d34d1fe2e1e090dfc0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/listing/tool/yjaxc/yjaxc-iframe.html" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 116, + "wire": "82864194b0a3a126a4aba0a3b093afe8739ce3acc85fa57f048663b858ace84fe3d8e190e0c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "realtime.search.yahooapis.jp" + }, + { + ":path": "/v1/post" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 117, + "wire": "8286d10499628346c545f0863c1a498a94309f052a628ed4a4f52f3a69a3e3e2e190e0c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/lib/news/widgets/tweet_button.html" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 118, + "wire": "8286d9048260e6e3d8e190e073ffbe039d29aee30c197f46a665fa56c1a91cc543141909355330939e863d741f9231eba0fc91634b60e92af3a69a3fc45840413a535aacc2a8b0aa2c3eba0fc917f439ce75c875fa56a8b09ccab3850ab37c751693a22a8be100089f6d51386559be203af3a07db65a544e78559bea89c1d732acdf0aa2712ab37fa2a272d559bf3a535aa26d98551362c2a89b1619ca3928354542fe8739ceb90ebf4ad51362c33d0a89b6708d5136cdf100220840cac0000006da12c81d48aceb46341551396165559bf3a535aa26d98551362c2a89b1619066a3d545f085fd0e739d721d7e95aa26c586522a26c585159ec4a151362c351abacf54482d862a151362c2a89b6708596c2fb2cb4fb4a89c2d44559bf8385e5b2eb544e22b190b924559bea89ce86479034012acdf544e27d565559bea89c2583f1470b28559bff08b0818274a6b55985516154587d741f922fe8739ceb90ebf4ad5161e8854587d741f922c6a925b2a1d0b463aaa2d8bf442ace135335b650ab37e74a6b544db30aa26c58551362c239d7f46a665fa56a89b161352398a8544d8b09a9544d8b09aaa8b60e4544d8b0aa270d4cd58d33aacdf8eab03121110626400584d817e95cca89c2506275b6ca1566fce94d6a89b661544d8b0aa26c586c917f439ce75c875fa56a89b1618fd9151362c28910a89b1617dd71a7951362c25ee9544db37df75c69e544d8b0fcce94d6a89b661544d8b0aa26c586832126aa65fd0e739d721d7e95aa26c587d455d87a4ea89b161a0c849aa9801544d8b0aa26d9c27544db37f2eb0b2d83fe0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yjaxc.yahoo.co.jp" + }, + { + ":path": "/oi" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/listing/tool/yjaxc/yjaxc-iframe.html?src0=http%3A%2F%2Fyjaxc.yahoo.co.jp%2Foi%3Fs%3Danemos_news01295%26i%3D2078709534%26w%3D%26apg%3D1%26t%3Dj%26u%3Dhttp%253A%252F%252Fheadlines.yahoo.co.jp%252Fhl%253Fa%253D20121103-00000542-sanspo-base%26ref%3Dhttp%253A%252F%252Fdailynews.yahoo.co.jp%252Ffc%252Fsports%252Fnippon_series%252F%253F1351933494%26enc%3DEUC-JP%26spaceId%3D%26jisx0402%3D%26type%3D%26crawlUrl%3D&src1=http%3A%2F%2Fyjaxc.yahoo.co.jp%2Fjs%2Fyjaxc-internal-banner.js%3Fimgurl%3Dhttp%253A%252F%252Fah.yimg.jp%252Fimages%252Fim%252Finnerad%252F%26imgpath%3Dbnr1_ss_1_300-250.jpg%26clickurl%3Dhttp%253A%252F%252Frd.yahoo.co.jp%252Fbzc%252Fsds%252F97648%252Fevt%253D97648%252F*http%253A%252F%252Flisting.yahoo.co.jp%252Fy_promo%252Flisting01%252F%253Fo%253DJP1350" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 119, + "wire": "8286c10486610461139fc7e4d9e290e1c2e0", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "headlines.yahoo.co.jp" + }, + { + ":path": "/sc/show" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 120, + "wire": "8286ce04a862393bb0171a65b698081e03c26d810022084016273181eeab3cf7447e4122401eb14cb0d798d2ffe4dbe290e1c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ah.yimg.jp" + }, + { + ":path": "/bdv/164354/1080825/20121101/hii0znrxvsbx0dt01k_g-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 121, + "wire": "8286d20498628346c545f0863c1a498a94306a473150c27c14a95ebaa6e4dbe290e173ffcd029d29aee30c197f46a665fa56c5068d8a8be10c7834931528613e0a54c51da949ea5e74d347f9140165b0bed3e179f7197be087b6a4c151ea2fc1a4813e0c9496c893e0a54c51da949ea885f140ea9a0e83f83d8698d50e88ac2ca5b0b6413a535aacc2a8b0aa2c339472506a8a85fd0e739d721d7e95aa2c33d0ab3846ab37c4008821032b0000001b684b207522b3ad18d05f8b0b21ac291307c24be5302b81b56ebaac2f2b81a56ec2add855c0caaf0557af2b830ab76f2afb2ae06d5bafab75a57032abc156eb8ae0655784abd0ab81c55f75585b57038abf79586f2b81a56ebcabc05706156ede55e0ab81b56ee05617d5c0dab75e56e815c0caaf0558702b81f55f795bb855c0faaf32ac2f2b81955e0aaf5e57038add0ab76157036abd7557efab81c55e7d57d95706156ede55e795c0caaf095badab81955e655bacab81955e12b742ae0655784ac2d2b81955e12b75f57032abccaafdf57032abccab76f2b81955e65579a5706156ede55e7d510165440e7fc2b81955e6557aeab81955e65585b57032abccab76f2b81955e12b75ff8b6ca209d29ad56615458551619ca3928354542fe8739ceb90ebf4ad51619e8559c23559be200441081958000000db425903a9159d68c682ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/lib/news/widgets/images/tweet.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/lib/news/widgets/tweet_button.html?_=1351949189638&count=none&id=twitter_tweet_button_2&lang=ja&original_referer=http%3A%2F%2Fheadlines.yahoo.co.jp%2Fhl%3Fa%3D20121103-00000542-sanspo-base&redirect=&text=%E5%B7%A8%E4%BA%BA%E3%81%8C%EF%BC%93%E5%B9%B4%E3%81%B6%E3%82%8A%E6%97%A5%E6%9C%AC%E4%B8%80%EF%BC%81%E5%BE%A9%E5%B8%B0%E3%81%AE%E9%98%BF%E9%83%A8%E3%81%8C%E6%B1%BA%E5%8B%9D%E6%89%93%EF%BC%88%E3%82%B5%E3%83%B3%E3%82%B1%E3%82%A4%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%84%EF%BC%89%20-%20Y!%E3%83%8B%E3%83%A5%E3%83%BC%E3%82%B9&url=http%3A%2F%2Fheadlines.yahoo.co.jp%2Fhl%3Fa%3D20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 122, + "wire": "8286d3049b628346c545f0863c1a498a94306a473150c27c14a98ba0d7aea9bf7abcd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102ef7da9677b8171707f6a62293a9d810020004015309ac2ca7f2c05c5c1dd518b2d4b70ddf45abefb4005db90408721eaa8a4498f5788ea52d6b0e83772ffc1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/lib/news/widgets/images/tweet_ja.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/lib/news/widgets/tweet_button.html?_=1351949189638&count=none&id=twitter_tweet_button_2&lang=ja&original_referer=http%3A%2F%2Fheadlines.yahoo.co.jp%2Fhl%3Fa%3D20121103-00000542-sanspo-base&redirect=&text=%E5%B7%A8%E4%BA%BA%E3%81%8C%EF%BC%93%E5%B9%B4%E3%81%B6%E3%82%8A%E6%97%A5%E6%9C%AC%E4%B8%80%EF%BC%81%E5%BE%A9%E5%B8%B0%E3%81%AE%E9%98%BF%E9%83%A8%E3%81%8C%E6%B1%BA%E5%8B%9D%E6%89%93%EF%BC%88%E3%82%B5%E3%83%B3%E3%82%B1%E3%82%A4%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%84%EF%BC%89%20-%20Y!%E3%83%8B%E3%83%A5%E3%83%BC%E3%82%B9&url=http%3A%2F%2Fheadlines.yahoo.co.jp%2Fhl%3Fa%3D20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 123, + "wire": "8286d60499628346c545f0863c1a498a94309f052a628ed4a4f52f3a69a3c053b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177bc090bfc7", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/lib/news/widgets/tweet_button.html" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 124, + "wire": "8286418caed9652d8b917f46a665fa5784c2539a352398ac5754df46a473158f9fbed00177bebe58f9fbed00176fc290c1c9", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "puffer.c.yimg.jp" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 125, + "wire": "8286bf84c3bec290c1c9", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "puffer.c.yimg.jp" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 126, + "wire": "8286bf84c3bec290c1c9", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "puffer.c.yimg.jp" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 127, + "wire": "8286bf84c3bec290c1c9", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "puffer.c.yimg.jp" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 128, + "wire": "828641881997f46a665fa57f049e60d48e62a18352c1a998d4b15904c1a9080010b4f8990564a6c0afd2b9bfc4bfc390c2c6", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/images/im/imgim/pc2/im1001149230pcmr1.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/listing/tool/yjaxc/yjaxc-iframe.html?src0=http%3A%2F%2Fyjaxc.yahoo.co.jp%2Foi%3Fs%3Danemos_news01295%26i%3D2078709534%26w%3D%26apg%3D1%26t%3Dj%26u%3Dhttp%253A%252F%252Fheadlines.yahoo.co.jp%252Fhl%253Fa%253D20121103-00000542-sanspo-base%26ref%3Dhttp%253A%252F%252Fdailynews.yahoo.co.jp%252Ffc%252Fsports%252Fnippon_series%252F%253F1351933494%26enc%3DEUC-JP%26spaceId%3D%26jisx0402%3D%26type%3D%26crawlUrl%3D&src1=http%3A%2F%2Fyjaxc.yahoo.co.jp%2Fjs%2Fyjaxc-internal-banner.js%3Fimgurl%3Dhttp%253A%252F%252Fah.yimg.jp%252Fimages%252Fim%252Finnerad%252F%26imgpath%3Dbnr1_ss_1_300-250.jpg%26clickurl%3Dhttp%253A%252F%252Frd.yahoo.co.jp%252Fbzc%252Fsds%252F97648%252Fevt%253D97648%252F*http%253A%252F%252Flisting.yahoo.co.jp%252Fy_promo%252Flisting01%252F%253Fo%253DJP1350" + } + ] + }, + { + "seqno": 129, + "wire": "8286418eb6ca10b8eb32e9f064a4b62e43d3048d602c5b65086087b6a4afd107abc553032a2f2ac590c4c7d6", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "urls.api.twitter.com" + }, + { + ":path": "/1/urls/count.json" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/lib/news/widgets/tweet_button.html?_=1351949189638&count=none&id=twitter_tweet_button_2&lang=ja&original_referer=http%3A%2F%2Fheadlines.yahoo.co.jp%2Fhl%3Fa%3D20121103-00000542-sanspo-base&redirect=&text=%E5%B7%A8%E4%BA%BA%E3%81%8C%EF%BC%93%E5%B9%B4%E3%81%B6%E3%82%8A%E6%97%A5%E6%9C%AC%E4%B8%80%EF%BC%81%E5%BE%A9%E5%B8%B0%E3%81%AE%E9%98%BF%E9%83%A8%E3%81%8C%E6%B1%BA%E5%8B%9D%E6%89%93%EF%BC%88%E3%82%B5%E3%83%B3%E3%82%B1%E3%82%A4%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%84%EF%BC%89%20-%20Y!%E3%83%8B%E3%83%A5%E3%83%BC%E3%82%B9&url=http%3A%2F%2Fheadlines.yahoo.co.jp%2Fhl%3Fa%3D20121103-00000542-sanspo-base" + }, + { + "cookie": "pid=v3:1351947306477664316206054; k=10.35.101.123.1351947536129989; guest_id=v1%3A135194753658491573; __utma=43838368.2140315505.1351947542.1351947542.1351947542.1; __utmb=43838368.2.10.1351947542; __utmz=43838368.1351947542.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)" + } + ] + }, + { + "seqno": 130, + "wire": "8286c284c6c1c590c4cc", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "puffer.c.yimg.jp" + }, + { + ":path": "/" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + } + ] + }, + { + "seqno": 131, + "wire": "8286cb04896251f7310f52e621ffc6c1c590c460ff71bb03ae7403e30bcf8dc9daf88e067e110023fb539e5d38396ebdab468c1a77c1240073d67c9e37b583a4b7b863d117ec3bf5efdf7978cae61f184bf96f98a2cc5e45ed45fccbc98fc66bfb39f69b2e1c7d1f5f1e5a34e2a77f8e53755e76a75d1c1ee316fbf2ed2598fc5fe99f95962331fceeb7c9b90f86b7b5f7c1c218f2f02bfe727575fce7e59ac71345fe9cb6f5f5778e78fb72ec9cb7621f9ddd47270d5f1de00fda9cf2e9c1cb761bb04904cd8a569b8dac1d0b97b7b1f571d734f5e3cedc32b98345f71cc3a3f724b4d7931322f5e27ad7fddbe5cc10caef7c57f3f4edd5d2ecc59a9c5377c2498ff1de00ff", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "headlines.yahoo.co.jp" + }, + { + ":path": "/favicon.ico" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b; YJNEWSCOMMENT=d=06yLIwT4EjfCUHM_ZATPTTC.be6FwFeXux__KeWeqlDK.dHwKDQYqgJFHj9.HJlNGmTwWgk.h4h.sU8V_TDfRcrHwDjLWrrsKoxSuxiWaUP8PvEUAbJUe9xIk79LoWKr6tlDjWRkyBVLbqWqtJB_axSkadUO&v=1; YJNEWSFB=d=g52f45b4EjeJqzak676NkVYuFf6EMD66FMZIfmpIG32ywhp.ZRx6EAf7vGDLjqk7eQGKmGgvFcgo&v=1" + } + ] + }, + { + "seqno": 132, + "wire": "8286cc04032f686cc7c4c690c5cdbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "headlines.yahoo.co.jp" + }, + { + ":path": "/hl" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?a=20121103-00000542-sanspo-base" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b; YJNEWSCOMMENT=d=06yLIwT4EjfCUHM_ZATPTTC.be6FwFeXux__KeWeqlDK.dHwKDQYqgJFHj9.HJlNGmTwWgk.h4h.sU8V_TDfRcrHwDjLWrrsKoxSuxiWaUP8PvEUAbJUe9xIk79LoWKr6tlDjWRkyBVLbqWqtJB_axSkadUO&v=1; YJNEWSFB=d=g52f45b4EjeJqzak676NkVYuFf6EMD66FMZIfmpIG32ywhp.ZRx6EAf7vGDLjqk7eQGKmGgvFcgo&v=1" + } + ] + }, + { + "seqno": 133, + "wire": "8286c1048e60d48e62a1849493b1192a0afd11c7bfc690c5739f9d29aee30c4e51c941aa2a17f439ce75c875fa56c4f47f848293a4ff09828f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/images/tech/bcn1.js" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=moto&t=l" + } + ] + }, + { + "seqno": 134, + "wire": "8286c204a762393bb0c844d30103acb4e898100220804fb14f5989e3da67a229e3aeaf3ea24d47586bcc697fc8c3c790c6be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/3124/1073472/20121029/mkgcwzthl_hbpnxy_tno-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=moto&t=l" + } + ] + }, + { + "seqno": 135, + "wire": "8286c204a862393bb0e85c13ec040eb4d85d60400882013ec75ec77ac9a3b621faeb4f9f32b24ed2ac35e634bfc8c3c790c6be", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/71629/1074517/20121029/kqo8rgbu_aykmxxf3cqf-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=moto&t=l" + } + ] + }, + { + "seqno": 136, + "wire": "8286418732fe8d4ccbf4af049a60d48e62a18a4b2186c7aa6d3306a666083b2cb0e989b5ebaa6fc9c4c890c7cd", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/media/ymui/img/carrrot_5.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://i.yimg.jp/images/news/v1/css/master-news.css?v11" + } + ] + }, + { + "seqno": 137, + "wire": "8286c304a662393bb027db7d8081e6c2275810022084026096203377afdd0eb511a8aa391ef542c35e634bc9c4c890c7bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/2959/1085127/20121102/crs1gvpzl74_ilnbd8yl-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=moto&t=l" + } + ] + }, + { + "seqno": 138, + "wire": "8286be04a660d48e62a18a8be10c47ea83545431dc400880fb148cd5311d56311d5644c801e5f02f5d537fc9c4c890c7bf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/news/bylines/v201209/main/bnr/bnr_300x90.png" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=moto&t=l" + } + ] + }, + { + "seqno": 139, + "wire": "8286418df5d07e48bfa1ce73ae43afd2bf048260e6cac2c990c8c06092bb03ae7403e30bcf8dc9daf88e067e110023", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yjaxc.yahoo.co.jp" + }, + { + ":path": "/oi" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=moto&t=l" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 140, + "wire": "8286c0049b60d48e62a18a8be10c27c19292d8c27c448acf1320044e017e95cdcbc6ca90c9c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/news/twitter/tw_spo_300_60.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=moto&t=l" + } + ] + }, + { + "seqno": 141, + "wire": "8286c504a662393bb017d9602075a65a030200441009f62c9eab51dcd3f8ae67facf38eb7fbd4b0d7e95cdcbc6ca90c9c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/193/1074340/20121029/rhnusvihwpg9khhap9vn-a.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=moto&t=l" + } + ] + }, + { + "seqno": 142, + "wire": "8286c504a762393bb0179c130103ccb8f8581002208190312f57bc7947bedea6a2b97c75191beb42c35e634bcbc6ca90c9c1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/1862/1083691/20121030/fk8wxszqyglpfwkac5kl-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=moto&t=l" + } + ] + }, + { + "seqno": 143, + "wire": "8286d004032f686ccbc8ca90c9c1c2", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "headlines.yahoo.co.jp" + }, + { + ":path": "/hl" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=moto&t=l" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b; YJNEWSCOMMENT=d=06yLIwT4EjfCUHM_ZATPTTC.be6FwFeXux__KeWeqlDK.dHwKDQYqgJFHj9.HJlNGmTwWgk.h4h.sU8V_TDfRcrHwDjLWrrsKoxSuxiWaUP8PvEUAbJUe9xIk79LoWKr6tlDjWRkyBVLbqWqtJB_axSkadUO&v=1; YJNEWSFB=d=g52f45b4EjeJqzak676NkVYuFf6EMD66FMZIfmpIG32ywhp.ZRx6EAf7vGDLjqk7eQGKmGgvFcgo&v=1" + } + ] + }, + { + "seqno": 144, + "wire": "8286c504a762393bb0e85c13ec040f082f818100220804fb11799fcceddbd64eeca1c39a63b51f6586bcc697cbc6ca90c9739f9d29aee30c4e51c941aa2a17f439ce75c875fa56c4f47f848107213e13051f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/71629/1082190/20121029/_xhxh5ukdv3s6oigo4bq-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=socc&t=l" + } + ] + }, + { + "seqno": 145, + "wire": "8286c604a762393bb0e85c13ec040eb6f3c060400882013ec77427a953d6ceecca4345ee5e80963586bf4ae6ccc7cb90cabe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/71629/1075880/20121029/vstketkrv3fci_zfj0fb-a.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=socc&t=l" + } + ] + }, + { + "seqno": 146, + "wire": "8286c604a662393bb0c818081d1080cb0200441009f62cf7dbaf99e91f5ead467eb6514bcec4b0d7e95cdfccc7cb90cabe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/30/1072203/20121029/rzqkxhmakk4bokrlm87_-a.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=socc&t=l" + } + ] + }, + { + "seqno": 147, + "wire": "8286c0048260e6ccc4cb90cabebf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yjaxc.yahoo.co.jp" + }, + { + ":path": "/oi" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=socc&t=l" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 148, + "wire": "8286c1049e60d48e62a18a8be10c52792da0ac5320801101d03f14c828ec24ebf4ae6fccc7cb90cabe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "i.yimg.jp" + }, + { + ":path": "/images/news/module/md20120709_gsearch.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=socc&t=l" + } + ] + }, + { + "seqno": 149, + "wire": "8286419821ea496a4afe8c5a24a4750e62d8b96498a8b4c92af5153f04a760693d2861d0b12bcc4b1b052b0e8657a58ca591f70a219197a469c65e91c238511485697e95cdcdc8cc90cbbf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "content.yieldmanager.edgesuite.net" + }, + { + ":path": "/atoms/71/f8/fb/ee/71f8fbeed96e2ac38d4638d6c6e2ece4.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=socc&t=l" + } + ] + }, + { + "seqno": 150, + "wire": "8286d204032f686ccdcacc90cbbfc4", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "headlines.yahoo.co.jp" + }, + { + ":path": "/hl" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=socc&t=l" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b; YJNEWSCOMMENT=d=06yLIwT4EjfCUHM_ZATPTTC.be6FwFeXux__KeWeqlDK.dHwKDQYqgJFHj9.HJlNGmTwWgk.h4h.sU8V_TDfRcrHwDjLWrrsKoxSuxiWaUP8PvEUAbJUe9xIk79LoWKr6tlDjWRkyBVLbqWqtJB_axSkadUO&v=1; YJNEWSFB=d=g52f45b4EjeJqzak676NkVYuFf6EMD66FMZIfmpIG32ywhp.ZRx6EAf7vGDLjqk7eQGKmGgvFcgo&v=1" + } + ] + }, + { + "seqno": 151, + "wire": "8286c704a762393bb0e85c13ec040eb2e05e60400882013ec26890ed52dce4b47d3c2687cb1d8eac35fa5737cdc8cc90cb739f9d29aee30c4e51c941aa2a17f439ce75c875fa56c4f47f848231a0bf09828f", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/71629/1073618/20121029/tldo4m5hcuajwtl9ebr7-a.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=base&t=l" + } + ] + }, + { + "seqno": 152, + "wire": "8286c804a762393bb0cb61698081d03ceb6c0801104201312199b653516d5d3c3f6d0fd567a990b0d7e95cdfcec9cd90ccbe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/3514/1070875/20121102/di3ufilunjw9ul9nrygs-a.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=base&t=l" + } + ] + }, + { + "seqno": 153, + "wire": "8286c2048260e6cec6cd90ccbec1", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yjaxc.yahoo.co.jp" + }, + { + ":path": "/oi" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=base&t=l" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 154, + "wire": "8286418a1d322e45fd1a9997e95f04c160d4c4834d36edfb30eec5b9cbb15b476d9f97d7ebb6eaf32cb2de42d816f4f32b767c0c0e9918100220840cac00000071e756f47a560000b0564cf6d31afd2b9bcfcace90cdbf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "amd.c.yimg.jp" + }, + { + ":path": "/im_siggSTQFSGS6B_ulqQXD.kRB.g---x150-y83-q90/amd/20121103-00000687-yom-000-1-thumb.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=base&t=l" + } + ] + }, + { + "seqno": 155, + "wire": "8286c904a662393bb0c818081d101f6d8100220804fb1178f7ebdb32a3dcfdbb9ed2389b47dd61afd2b9bfcfcace90cdbf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/30/1072095/20121029/_wzyz3fszhqvouc6tuav-a.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=base&t=l" + } + ] + }, + { + "seqno": 156, + "wire": "8286c004a860693d28604cb00658942c3aeb02640cca175d7de94010b91b6f9246d99638db95e7de7595fa5737cfcace90cdbf", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "content.yieldmanager.edgesuite.net" + }, + { + ":path": "/atoms/23/03/f1/77/2303f17798f0116b59cd53fbb5f89873.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=base&t=l" + } + ] + }, + { + "seqno": 157, + "wire": "8286d404032f686ccfccce90cdbfc6", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "headlines.yahoo.co.jp" + }, + { + ":path": "/hl" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=base&t=l" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b; YJNEWSCOMMENT=d=06yLIwT4EjfCUHM_ZATPTTC.be6FwFeXux__KeWeqlDK.dHwKDQYqgJFHj9.HJlNGmTwWgk.h4h.sU8V_TDfRcrHwDjLWrrsKoxSuxiWaUP8PvEUAbJUe9xIk79LoWKr6tlDjWRkyBVLbqWqtJB_axSkadUO&v=1; YJNEWSFB=d=g52f45b4EjeJqzak676NkVYuFf6EMD66FMZIfmpIG32ywhp.ZRx6EAf7vGDLjqk7eQGKmGgvFcgo&v=1" + } + ] + }, + { + "seqno": 158, + "wire": "8286c904a862393bb0e85c13ec040eb4d85d60400882013ec76f54b688a5b4f575f12ced77f0e01e586bcc697fcfcace90cd739e9d29aee30c4e51c941aa2a17f439ce75c875fa56c4f47f8481159fe13051", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/71629/1074517/20121029/qym5s_fuonkwfh4vw608-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=spo&t=l" + } + ] + }, + { + "seqno": 159, + "wire": "8286ca04a962393bb0e85c13ec040eb2e05a60400882013ec78a91d633592f497a7aeddba78e95796561afd2b9bfd0cbcf90cebe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/71629/1073614/20121029/wnskbirfjfjyqqjwjnx3-a.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=spo&t=l" + } + ] + }, + { + "seqno": 160, + "wire": "8286c4048260e6d0c8cf90cebec3", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yjaxc.yahoo.co.jp" + }, + { + ":path": "/oi" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "*/*" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=spo&t=l" + }, + { + "cookie": "B=76j09a189a6h4&b=3&s=0b" + } + ] + }, + { + "seqno": 161, + "wire": "8286ca04a762393bb027db7d8081e6c22698100220840261d097c5824cf44bda217a376f53f4f0b0d798d2ffd0cbcf90cebe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/2959/1085124/20121102/71ewr2thlfq_2yiqyhjw-a.gif" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=spo&t=l" + } + ] + }, + { + "seqno": 162, + "wire": "8286ca04a662393bb0c818081d1040e30200441009f62727b30dff0e7bf6667a23afbabbf93ac35fa5737fd0cbcf90cebe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/30/1072106/20121029/hczia9w6zzi3jskznvxo-a.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=spo&t=l" + } + ] + }, + { + "seqno": 163, + "wire": "8286ca04a762393bb027000798081d75d138c0801104027d8f768e31cd44d223c1ab67c798a51f8586bf4ae6d0cbcf90cebe", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "ai.yimg.jp" + }, + { + ":path": "/bdv/26008/1077726/20121029/zuabaglgdswip3wx_faw-a.jpg" + }, + { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" + }, + { + "accept": "image/png,image/*;q=0.8,*/*;q=0.5" + }, + { + "accept-language": "en-US,en;q=0.5" + }, + { + "accept-encoding": "gzip, deflate" + }, + { + "connection": "keep-alive" + }, + { + "referer": "http://headlines.yahoo.co.jp/hl?c=spo&t=l" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_21.json b/http/http-client/src/test/resources/hpack-test-case/story_21.json new file mode 100644 index 000000000..f8032bc74 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_21.json @@ -0,0 +1,16154 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "488264016196dc34fd280654d27eea0801128166e01ab82714c5a37f7685dc5b3b96cf0f1f919d29aee30c78f1e171d23f67a9721e963f0f0d8213204088ea52d6b0e83772ff8c49a929ed4c02fa5291f98040408721eaa8a4498f5788cc52d6b4341bb97f5f95497ca589d34d1f6a1271d882a60320eb3cf36fac1f", + "headers": [ + { + ":status": "301" + }, + { + "date": "Sat, 03 Nov 2012 13:04:26 GMT" + }, + { + "server": "Server" + }, + { + "location": "http://www.amazon.com/" + }, + { + "content-length": "230" + }, + { + "keep-alive": "timeout=2, max=20" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "text/html; charset=iso-8859-1" + } + ] + }, + { + "seqno": 1, + "wire": "885f87352398ac5754df0f0d8371b75d7f0188ea52d6b0e83772ff6196df697e94132a6a225410022502edc6deb8d3aa62d1bfc45891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96c361be940bea6a225410022504cdc6dfb8dbca62d1bf55857d913ad03f4089f2b0e9f6b12558d27fadf139af453e9a6ecd66a69eccb61ee187b51879ad78d33812f9a247f67e4cfbfddadbe359dfebee5ed81fd9041f7caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "6577" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 23 Oct 2012 17:58:47 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 19 Oct 2012 23:59:58 GMT" + }, + { + "age": "932740" + }, + { + "x-amz-cf-id": "whiC_hNmBgrO48K-Fv1AqlFY-Cig61exld9QXg99v4RwPo9kzfqE9Q==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 2, + "wire": "885f87352398ac4c697f0f0d023433c76196e4593e94138a6e2d6a0801128215c0bd719754c5a37fcd6c96df697e94136a6e2d6a0801128205c139704153168dffc7c65585644d3efb607f05accf7a8b3af0a2c12cf9cb90fa7c6b5af79f38add14e5d935bbc34f1d384939ab4e9fcde29badb6611849e2083c4c3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 26 Sep 2012 22:18:37 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 25 Sep 2012 20:26:21 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "3249950" + }, + { + "x-amz-cf-id": "LClrkUlr2-9oeIoNwP-CxxGuMmJQguT1mVNFchiptNXT2gkurFa1cw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 3, + "wire": "88cc0f0d8365d75acb6196df697e94640a6a225410022504cdc00ae34d298b46ffd1cac96c96df697e94640a6a225410022504cdc00ae34d298b46ff5585640fba067f7f02ac06eab164af0831d07365be9bbb91d6b067d64a2f6a93d2c4bb8eea35b39f7f092efa58140a7a79ede42f1041c8c7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "3774" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 30 Oct 2012 23:02:44 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 30 Oct 2012 23:02:44 GMT" + }, + { + "age": "309703" + }, + { + "x-amz-cf-id": "0SnGIpF0HloiJDtBSskp0LPclCOdy-cBHBsP3LTUdBy-0l2hmYRW2w==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 4, + "wire": "88d5d40f28ba4753550547475355f6a5634cf031f6a487a466aa05c748fd9ea5c87a7ed42f9acd615106e1a7e94032b693f7584008940b3700d5c138a62d1bff4085aec1cd48ff86a8eb10649cbf4088f2b0e9f6b1a4583f91063c0e6efd6f1b7fad73743ba16dacafff4003703370ff12acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7eaf6b83f9bd0ea52feed6a67879297b86d521bfa14c9c613a9938df3a97b5693a9ab7eb3a9ab86d52fe0ce6535f0ba65356fda652ef0dca6bc7cd4d5a73a9c34e4535f0daa61c9a54bdab429a61e2a64d3bd4bf834297b4ef5376f854c7821535edc0a67d5794c5ab8a9ab7de53f95886a8eb10649cbf64022d314088f2b0e9f6b1a4585fb4d1da49b466f36871b92cdfad13f39b12e8d9ebcdee36d7c534c86859a77f620aa6bfdc63c70b0d68c3819be9e28bb8dcbad4f5ff7b9384842d695b05443c86aa6fae082d8b43316a4f5a839bd9ab5f96497ca589d34d1f6a1271d882a60c9bb52cf3cdbeb07f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dff798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:26 GMT" + }, + { + "server": "Server" + }, + { + "set-cookie": "skin=noskin; path=/; domain=.amazon.com; expires=Sat, 03-Nov-2012 13:04:26 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-amz-id-1": "0HE6SZ5H5Z4Y71SA54J9" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "-1" + }, + { + "x-amz-id-2": "MqdgMKxu1H6fgZ4cXY/fMQyxCVupVtmdiA3mTqc2n4+baHA/4MFE3DtVsBH6B4hp" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html; charset=ISO-8859-1" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 5, + "wire": "88d00f0d023433d96196df697e940854dc5ad410022502cdc64371b0a98b46ffdfd8d76c96c361be940894dc5ad41000f28105c0b3719694c5a37f558669b75d6db73f7f0cad87bcc6fd4b9b3be2365e9c6b49aaf3a39211da4c0f0a1df6eef3402fad825d9dbacce1b8e8bb7a6cbd64d9041fd6d5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 11 Sep 2012 13:31:51 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 12 Sep 2008 10:13:34 GMT" + }, + { + "age": "4577556" + }, + { + "x-amz-cf-id": "AvgiZt6QvGiJjVptinxMWssqdE82ATuSxl0D-EfQqkg6iVMBCgJkdQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 6, + "wire": "88d40f0d8375e0b3dd6196c361be940094d27eea0801128076e001700153168dffe3dcdb6c96c361be940094d27eea0801128066e341b82654c5a37f5585081e138e7f7f02abbc0d6d1ab77d76d4823351b744fdd91bd5a7dd75e50c52496e0cee97f0ba28bd91079f00348e401af78820dad9408725453d44498f57842507417f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "7813" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 02 Nov 2012 07:00:01 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 03:41:23 GMT" + }, + { + "age": "108266" + }, + { + "x-amz-cf-id": "C0P4ip7yqOsc3niS_9Bd5ONzppJ1_dduEL7eXeMlCIsohE0Nad0iCw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 7, + "wire": "885f88352398ac74acb37f0f0d83136067e36196df697e9413ea693f7504008540b971a72e32f298b46fe96c96e4593e940094cb6d4a0801028176e36ddc6da53168dfe3e2558613ecb8271c7f7f04aededcf92ad51b93f0d5d9ad3bb9efe297b7f445edd8b6f0c63975f9f18baee6fa1b4e2f75e7a4de6ff0a795b34107e0df", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2503" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 29 Nov 2011 16:46:38 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 02 Jun 2010 17:55:54 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "29362669" + }, + { + "x-amz-cf-id": "T5hInOb6hUOq4NSYTVt8TjsCSGRUHafPxwGkS5jiNGzpLmixDUmWug==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 8, + "wire": "88de0f0d8375a683e76196c361be9413ca6e2d6a080112816ae36fdc13ca62d1bfed6c96df3dbf4a019532db52820040a0037001b8d054c5a37fe7e65585642065a7df7f02ad7abb7b55c3417d4e0c0fdfbd659d391efb69befe2e9da6c5eac707a3d4cb2ddb3a05ed382367664e8fe3d9041fe4e3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "7441" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 28 Sep 2012 14:59:28 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 01:01:41 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "3103499" + }, + { + "x-amz-cf-id": "8puqnUMeyh0E9DCrrjWoD5tD9GjqgGyr6aMyg--qLs2ztEb3QIj9HQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 9, + "wire": "88c60f0d83682277eb6196df3dbf4a09d53716b50400894106e34e5c680a62d1bff1eae96c96e4593e940894dc5ad410022504cdc69bb820298b46ff5586642e36d38eff7f02aecfa0f267cbd2edbc38338fa7307b1fa1c84dfc511517f5ecccb1f26667972e79687fd75db83dcba7252fe8f1041fe8e7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4127" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 27 Sep 2012 21:46:40 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 12 Sep 2012 23:45:20 GMT" + }, + { + "age": "3165467" + }, + { + "x-amz-cf-id": "LModLJjBuUU3HjY0zayadcTVs_lDPQK-oIK3WWYJl9ykREzfNIm9Mw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 10, + "wire": "88ca0f0d83642cb7ef6196dd6d5f4a05c53716b504008940b371a7ee32053168dff5eeed6c96df3dbf4a05953716b5040089403571a72e32d298b46f5586682d34d3eeff7f02ac7356144afd5aed5afb517ae5b3d328c9a7de91faf53392d2e09bcb2fef126ba7b739351884369019bb26820feceb", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3135" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 16 Sep 2012 13:49:30 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 13 Sep 2012 04:46:34 GMT" + }, + { + "age": "4144497" + }, + { + "x-amz-cf-id": "6OFsf9nPu-D4_yWQy3sINzNayyg6fm625JfZVcPmqYdOicciN0i5rg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 11, + "wire": "88ce0f0d83640cbff36196dd6d5f4a01a5340ec5040089408ae34d5c036a62d1bf7685dc5b3b96cff3f20f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96c361be940094d03b1410022502f5c69cb80694c5a37f55861040f09e10bf7f03acb1df4cdf0344d9ebf7906be6dbf64f2e63f2bf6de7fd6396b6cb575941e9f1b97730f0c4e6b25fe333b34107f1f0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3039" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 04 Mar 2012 12:44:05 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Fri, 02 Mar 2012 18:46:04 GMT" + }, + { + "age": "21082822" + }, + { + "x-amz-cf-id": "r7y3D04cQyZW1pY59rhfKoWDuC9yHfp5enkf0y9a6BKaF_6PcDVg7g==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 12, + "wire": "88d30f0d83684217408721eaa8a4498f5788ea52d6b0e83772ff6196d07abe940b8a65b68504008940bb7001b8d3ca62d1bfc35891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96d07abe941094cb6d4a08007d408ae005702053168dff55867da79f75b7ff7f05ace34ea2ce6b5ca6ff0bb5efc7ea8a4d555de426ef6eebf6cc7e218a4d26a1c3753b3db264cd6b55f7f1b268207caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4222" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 16 Jul 2012 17:01:48 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 22 Jun 2009 12:02:10 GMT" + }, + { + "age": "9489759" + }, + { + "x-amz-cf-id": "VmOehiu6mDUBpTHylminnvdcSz7Pz3bwA_dNil6iko3qIIKu4pvwQg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 13, + "wire": "88dc0f0d830ba10fc66196d07abe9413ca693f75040085400ae04171a0a98b46ffcbc5c46c97df3dbf4a05b5349fba82001d500d5c13771b6d4c5a37ff558613ed802e09cf7f04adf17e7e002d97bc0399fd6d9a1e941cde7c1bd27037924c3bd6b2f6f6f26ff1e75f96edc336287bb60e6fec820fc3c2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1711" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 28 Nov 2011 02:10:41 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 15 Nov 2007 04:25:55 GMT" + }, + { + "age": "29501626" + }, + { + "x-amz-cf-id": "wDhU0erCw0YoyRgAjloixwiytE5IdFT-rCT5ITwxPx5uFgGAv50Y9Q==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 14, + "wire": "88e00f0d8365d107ca6196c361be940bca693f75040085410ae099b8c854c5a37fcfc9c86c96df697e94034a651d4a08010a817ae34f5c1014c5a37f558664027c4f36e77f02aee7f9a712d573d06c1c4c0987b7166b2f64974eabfafbe4cdab0379d693deb2fe7bf5ce70e7b766fcfd3bc9ba1820c7c6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3721" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 18 Nov 2011 22:23:31 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 04 Jan 2011 18:48:20 GMT" + }, + { + "age": "30292856" + }, + { + "x-amz-cf-id": "YXNG-nYMiEVi0gaRGKrCIfNODPvIKOE5L-dzPeXzyYh1LuQTLjvdSA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 15, + "wire": "88e40f0d84134cb8ffce6196dc34fd280654d27eea0801128076e001700053168dffd3cdcc6c96dc34fd280654d27eea0801128066e34cdc684a62d1bf5584105e71df7f02ac10d0341fd13b17c5bd1bc3e2a73c394f4bddf56893f767ee7fd9f4cf250d35cfd1c2b4391f9f63b1e1966820cbca", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "24369" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 03 Nov 2012 07:00:00 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Sat, 03 Nov 2012 03:43:42 GMT" + }, + { + "age": "21867" + }, + { + "x-amz-cf-id": "2asasoycqewuj5Fwn6w6mjCvOMdZQZLZhNhdl44Yyo1-AI9hQ7bFfg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 16, + "wire": "885f87352398ac4c697f0f0d830882e7d36196dc34fd282794cb6d0a080112806ee01bb8dbaa62d1bfd8d2d16c96d07abe9413ca612c6a08010a817ae32e5c0894c5a37f558579a7db7c207f03ad72767536e484e9eb6323d1152d8de88d62dc66e3eefdea49e3d64e1f987773267c393664ddeba37b1a73bc3041d0cf", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1216" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 28 Jul 2012 05:05:57 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 28 Feb 2011 18:36:12 GMT" + }, + { + "age": "8495910" + }, + { + "x-amz-cf-id": "6h3O56dcjyQ3aM_m5a8_ir-VgVzDCmcwyIUXFSYcLFIQISyj5Q46vA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 17, + "wire": "88ed0f0d8469d75c77d76196c361be940bea6a225410022502d5c6c571a794c5a37fdcd6d56c96d07abe9403ca6a2254100225042b8c82e36da98b46ff5585089e7da7df7f02ad46a735f6be2f583b0f5d743783be9e75624579ad9ed87f11c59e92adcc61353c82279ddb9cfe3bf1d9bf64107fd4d3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "47767" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 19 Oct 2012 14:52:48 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 08 Oct 2012 22:30:55 GMT" + }, + { + "age": "1289499" + }, + { + "x-amz-cf-id": "sO6PqD2yEqaPpl5EvNYnGspKuhuAXsV3jf-Ya1imW1287RLowvVQTQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 18, + "wire": "88f10f0d8369c0b3db6196dd6d5f4a09d5349fba820042a099b8d3571b1298b46fe0dad96c96d07abe941054d27eea08010a820dc6857196d4c5a37f558613ed840cbadf7f02adeddde69abffbe6dcf4755b3e2e815a1cba6be9ee2cfabde7da22624598c79f6d2ff4871a7a03d55adc724d9041d8d7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4613" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 27 Nov 2011 23:44:52 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 21 Nov 2011 21:42:35 GMT" + }, + { + "age": "29510375" + }, + { + "x-amz-cf-id": "qv844DZxuLlk-LGj1-AJNpjz_LOzLR2cGsrHaLRm9jAHtj0ynP66dQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 19, + "wire": "88f50f0d8365f65adf6195dc34fd282029a88950400894002e00371a0a98b46fe4dedd6c96c361be9413ca6e2d6a0801128166e320b8cbaa62d1bf5585089b71b71c7f02adcbc9bd9eade58af8db3757e1c9811769ab8b9cecf49d6fc2969eb13578e5d3375a9d76cfcd5d1e2797f943041fdcdb", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3934" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 20 Oct 2012 00:01:41 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 28 Sep 2012 13:30:37 GMT" + }, + { + "age": "1256566" + }, + { + "x-amz-cf-id": "JW5QyuWGDa5ik9AIEsBmnV6YrytP9At48rtnwWjKkn77rXOj8cx9WA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 20, + "wire": "885f88352398ac74acb37f0f0d830b2d35e46196dc34fd280654d27eea0801128005c6dfb8c814c5a37fe9e3e20f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96e4593e94134a6a225410022502f5c6c3700f298b46ff558469969f777f03adc99a8f3ca923d1fac78e2c39709a33f64df74327b0635b175ba3f4cee1e2139a3c68bdf92e79531639b9f8820fe1e0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1344" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 03 Nov 2012 00:59:30 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Wed, 24 Oct 2012 18:51:08 GMT" + }, + { + "age": "43497" + }, + { + "x-amz-cf-id": "IKlxWmc8byHH_FJFiboqtD71dz0H-GkBay3SaG26MwMCXfLft_HgYw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 21, + "wire": "88ec6c96e4593e940b6a436cca0801128215c6c371b654c5a37f5f86497ca582211f7b8b84842d695b05443c86aa6f5a839bd9ab5893aed8e8313e94a47e561cc581c134c81d79d0ff6496d07abe940b8a436cca080c89403b71b6ee32f298b46f6196dc34fd280654d27eea0801128166e01ab82754c5a37f0f0d836d96daef", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 15 Aug 2012 22:51:53 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=624307871" + }, + { + "expires": "Mon, 16 Aug 2032 07:55:38 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:27 GMT" + }, + { + "content-length": "5354" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 22, + "wire": "88f36c96e4593e94642a6a225410022502cdc13771b0298b46ffc4c3c25893aed8e8313e94a47e561cc581c640d3827d917f6496df697e94138a6a22541019128166e32fdc6df53168dfc10f0d8365c6def2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 31 Oct 2012 13:25:50 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=630462932" + }, + { + "expires": "Tue, 26 Oct 2032 13:39:59 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:27 GMT" + }, + { + "content-length": "3658" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 23, + "wire": "88f66c96df697e94132a6a225410022500f5c002e36ca98b46ffc7c6c55893aed8e8313e94a47e561cc581c13ef3c2780d7f6496df697e940bea6a22541019128205c643702153168dffc40f0d8375c645f5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 23 Oct 2012 08:00:53 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=629882804" + }, + { + "expires": "Tue, 19 Oct 2032 20:31:11 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:27 GMT" + }, + { + "content-length": "7632" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 24, + "wire": "885f87352398ac5754df0f0d8369e035f66196e4593e94642a6a2254100225041b8db3700ea98b46ff7685dc5b3b96cff6f56c96d07abe9413ea6a2254100225041b8105c034a62d1bff5585109d69e0ff7f11ac8e4f5c79cd2bb19f93e0eb3f07efbfd776c6ac4bd8da0cf17ab65add7fddfcf159c61786fc767b164f10c107f4f3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "4804" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 21:53:07 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 29 Oct 2012 21:10:04 GMT" + }, + { + "age": "227481" + }, + { + "x-amz-cf-id": "bdyVYgf7boW90khU9D9kSQ4rt8H41h_yufp79zDL_rVA8a9brz2IwA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 25, + "wire": "88c30f0d03313438408721eaa8a4498f5788ea52d6b0e83772ff6197dd6d5f4a05b532db42820044a059b8dbb719654c5a37ffc35891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96d07abe94005486d99410021504cdc64571b714c5a37f55857db79d105b7f05aec5ddc4e59daef978e1c9aaef9a11af9f38aa5a9b6bfde7abb23b022c9f98f7fb7396e79fedca1b3d6acde9e9a0837caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "148" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 15 Jul 2012 13:57:33 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 01 Aug 2011 23:32:56 GMT" + }, + { + "age": "9587215" + }, + { + "x-amz-cf-id": "Gv6tJh4vJVFIOBxlsPYY_n-mupZYOqsq0_IXHTz6WS89qWAryOKy8g==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 26, + "wire": "88cc0f0d03333536c66196c361be9413ca6e2d6a080112800dc699b8cbaa62d1bfcbc5c46c96df3dbf4a082a65b6850400854102e340b8dbea62d1bf5585642d844d877f04ac85a2e9f2d79bb0daf597f345bb774e325e3f33b6fd9908d8eee1ae70d45c65ecfba79f17af70e9d329e1820fc3c2408725453d44498f57842507417f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "356" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 28 Sep 2012 01:43:37 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 21 Jul 2011 20:40:59 GMT" + }, + { + "age": "3151251" + }, + { + "x-amz-cf-id": "A4eNx4xBAu8rDK_SSjVdCoYo59rIc5aBFph1neHeq97ohGyzANNfoA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 27, + "wire": "88f40f0d8371f79ccb6196dd6d5f4a32053716b50400894082e36fdc69f53168dfd06c96df3dbf4a019532db52820040a003700e5c644a62d1bfcbca558513ed36075f7f03acbb7bdbd937f4a3070d352c9865295bc9b39eff189f1ca725314417345239f764bd497b75f4f269eeb7686083c8c7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "6986" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 30 Sep 2012 10:59:49 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 01:06:32 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "2945079" + }, + { + "x-amz-cf-id": "BCz8ITjlEUNn-tAfee5IQYTwG9afocm__16MmahSICmeqky8tmv-qA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 28, + "wire": "886196dc34fd280654d27eea0801128166e01ab82794c5a37fd40f28ba4753550547475355f6a5634cf031f6a487a466aa05c748fd9ea5c87a7ed42f9acd615106e1a7e94032b693f7584008940b3700d5c138a62d1bff4085aec1cd48ff86a8eb10649cbf4088f2b0e9f6b1a4583f91063c0e6efd6f1b7fad73743ba16dacafff4003703370ff12acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7eaf6b83f9bd0ea52feed6a67879297b86d521bfa14c9c613a9938df3a97b5693a9ab7eb3a9ab86d52fe0ce6535f0ba65356fda652ef0dca6bc7cd4d5a73a9c34e4535f0daa61c9a54bdab429a61e2a64d3bd4bf834297b4ef5376f854c7821535edc0a67d5794c5ab8a9ab7de53f95886a8eb10649cbf64022d314088f2b0e9f6b1a4585fb4d1da49b466f36871b92cdfad13f39b12e8d9ebcdee36d7c534c86859a77f620aa6bfdc63c70b0d68c3819be9e28bb8dcbad4f5ff7b9384842d695b05443c86aa6fae082d8b43316a4fe75f87497ca589d34d1f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dff798624f6d5d4b27f408cf2b0e9f752d617b5a5424d279a96591b132d32b09b8dc582128961902eacdbae3600b323aebedf5892a47e561cc5804f819034007d295db1d0627f0f0d83085a074084f2b563938d1f739a4523b0fe105b148ed9bf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:28 GMT" + }, + { + "server": "Server" + }, + { + "set-cookie": "skin=noskin; path=/; domain=.amazon.com; expires=Sat, 03-Nov-2012 13:04:26 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-amz-id-1": "0HE6SZ5H5Z4Y71SA54J9" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "-1" + }, + { + "x-amz-id-2": "MqdgMKxu1H6fgZ4cXY/fMQyxCVupVtmdiA3mTqc2n4+baHA/4MFE3DtVsBH6B4hp" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "x-amzn-requestid": "ffd52343-25b6-11e2-ac17-5765013d7795" + }, + { + "cache-control": "max-age=29030400, public" + }, + { + "content-length": "1140" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 29, + "wire": "887689bf7b3e65a193777b3f5f911d75d0620d263d4c795ba0fb8d04b0d5a70f0d03323936eecc", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "296" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:04:28 GMT" + } + ] + }, + { + "seqno": 30, + "wire": "88be0f0d836dd13dde6196e4593e940814d444a820044a00371966e09e53168dffe3dddc6c96dc34fd282129a88950400854002e340b8d814c5a37ff558379f0337f11ad3c9eeaddde1c9ea5eeee9a7b7efe39088fbf1e08b27e3764ef3cff7ba3d79a5f15cccc3a0eff567713f090c107dbda", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "5728" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 10 Oct 2012 01:33:28 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Sat, 22 Oct 2011 00:40:50 GMT" + }, + { + "age": "8903" + }, + { + "x-amz-cf-id": "odznSvAIyfv7NmqZX6A2oTHE_IX5rh889vBaPKfwpg3AMo9k3ScXcA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 31, + "wire": "885f8b497ca58e83ee3412c3569f0f0d8371f6c3d1e7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/javascript" + }, + { + "content-length": "6951" + }, + { + "date": "Sat, 03 Nov 2012 13:04:28 GMT" + }, + { + "server": "Server" + } + ] + }, + { + "seqno": 32, + "wire": "885f87352398ac4c697f0f0d840baf34ffe47f0db5fd9a4dbea4efe788bf5be7ae5a1fef3fefeefe7cb46afc77bd0f77c897f6dba37d1489b7b1c7ce6f21f5a27c68fc371dfb5bde1f1f408cf2b0e9f6b585ed6950958d278c0b2d85e15d6c2e05ebe27ddf6196e4593e940bca65b685040089410ae36edc136a62d1bf6c96e4593e940bca65b685040089410ae34cdc138a62d1bf0f1399fe40f46095975f68a18db2be37c4eb8fbc50b6fbad048d83f952848fd24a8f76868691fb3d5b9955846c0e38e77f08ad6bc138cf6b3ab01b94b87bf3ef819c58dbe1cf5359b9bdc2baa8ce4cbb76ededfb383607de0cf6536434a218207caf0ae0508cb2592395b69b7c6d0da69d69f20891b652491b32906b9283db24b61ea4af5152a7f57a83db261b0f527fbfe5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "17849" + }, + { + "connection": "keep-alive" + }, + { + "x-amz-id-2": "ZgdRydvxV2Z5YPfl9vhZZTYWMOX7vl8vIt9RuMTlm258HbYgx1yMhHsXiVTR5T1w" + }, + { + "x-amz-request-id": "135182B51618D297" + }, + { + "date": "Wed, 18 Jul 2012 22:57:25 GMT" + }, + { + "last-modified": "Wed, 18 Jul 2012 22:43:26 GMT" + }, + { + "etag": "\"08b0f3794e1b5e9a927698e159741c50\"" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "AmazonS3" + }, + { + "age": "50666" + }, + { + "x-amz-cf-id": "4wcVhu3OEiWfFvYvE3GH5UYO4KY8UpnlLcJRRRqZh0Q1zELrmrAmsA==" + }, + { + "via": "1.0 c33edbf5459a4a44749c2cb5ecdb3fca.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 33, + "wire": "88f16c96df697e94640a6a2254100225002b8c86e004a62d1bffce7b8b84842d695b05443c86aa6f5a839bd9ab5893aed8e8313e94a47e561cc581c640d084eb6cff6496df697e94138a6a22541019128015c641704053168dff6196dc34fd280654d27eea0801128166e01ab82754c5a37f0f0d846da105dff3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 30 Oct 2012 02:31:02 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=630422753" + }, + { + "expires": "Tue, 26 Oct 2032 02:30:20 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:27 GMT" + }, + { + "content-length": "54217" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 34, + "wire": "885f87352398ac5754df0f0d830bc277f46196df3dbf4a09d53716b5040089410ae04571b794c5a37f7685dc5b3b96cff4f36c96d07abe940894d03b1410022504cdc6ddb81654c5a37f5585642e32f3e17f0badf1c220232b5c72f9cd774ac1a917e19a68b370a65e31d631e2f47779968e0ff8c0fe5a7162c59b63e2c1b2083ff2f1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "1827" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 27 Sep 2012 22:12:58 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 12 Mar 2012 23:57:13 GMT" + }, + { + "age": "3163891" + }, + { + "x-amz-cf-id": "wU_0sJ4VJxKBN-1nsDAgg_KUmfVbpaaGyo7YelU9wE9JmGGGKQ92EQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 35, + "wire": "88c30f0d8308596b408721eaa8a4498f5788ea52d6b0e83772ff6196df697e9403ea6a225410022502f5c03f702ea98b46ffc35891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46fc45585105a0ba1177f04aedc7d99f7519fc67aa2e9260d5e8df98bc4c6ab9facd5cf5f8e1b9c5a48f633e2bd7daee5c9b66729d5666efb2083f8f7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "1134" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 09 Oct 2012 18:09:17 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 12 Mar 2012 23:57:13 GMT" + }, + { + "age": "2141712" + }, + { + "x-amz-cf-id": "SoQLSlLwLn_jdEOyiXGwginYyKphpwUS6-dbQ3wpPqBJIRg6mOrKvQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 36, + "wire": "88d90f0d03363336c36196dc34fd282029a889504008940b971b66e01b53168dffc8c2c16c96e4593e940094cb6d4a0801028215c0bd71a794c5a37f5585085f6de79a7f02ae4f8c72f4fcdfc9b9f0d17e17f31fb66e37797e7aea8c736ed3335c67f4cd284e0dfdfd6bc76337abfdece45b20837caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "636" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 20 Oct 2012 16:53:05 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 02 Jun 2010 22:18:48 GMT" + }, + { + "age": "1195884" + }, + { + "x-amz-cf-id": "twHfjXTW5hFlDA9KoqKVBWXyksHgSNg4Vhy3mstETvyPHr3CpZq6_Q==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 37, + "wire": "88df0f0d03323436c96196df3dbf4a09c532db42820044a041704f5c644a62d1bfcec8c76c96e4593e940094cb6d4a0801028215c0bf7190298b46ff558679c69f65b77f7f04acc56c3e725590c3d856b0ed337e2ef0fcef44d7bb466fca3b5d1e1461e915442567efcb046141759f5c9b2083c3c2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "246" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 26 Jul 2012 10:28:32 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 02 Jun 2010 22:19:30 GMT" + }, + { + "age": "8649357" + }, + { + "x-amz-cf-id": "GuAxInIiaQe4FRi5wBUXvlgCqbiXlqBaFsFj_nccpovWEb1sePoPdQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 38, + "wire": "886196dc34fd280654d27eea0801128166e01ab827d4c5a37fd20f28ba4753550547475355f6a5634cf031f6a487a466aa05c748fd9ea5c87a7ed42f9acd615106e1a7e94032b693f7584008940b3700d5c138a62d1bfff77f37910378716d73066dda6febfdbd81bdfb72ecf6f56401307f26b4dfd071b3af2f5efcb168f5b84da3a25dbd5e51128272dfe5d170e8debb24db8ecbd93ada2af1f4776d5bfc5027d9fddf2f3d1f9ff4db5f92497ca589d34d1f6a1271d882a60e1bf0acf70f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dfff3f2f10f0d83085a07f0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:29 GMT" + }, + { + "server": "Server" + }, + { + "set-cookie": "skin=noskin; path=/; domain=.amazon.com; expires=Sat, 03-Nov-2012 13:04:26 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-amz-id-1": "05FGR6EKSNDPZCE5TRJQ" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "x-amz-id-2": "Tjab3PJkvWGMyS25sjt7CpJ2clcWTx72Uj5PrdRHrCIku2pHj7RnTwl293ZTfYMX" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html; charset=UTF-8" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "x-amzn-requestid": "ffd52343-25b6-11e2-ac17-5765013d7795" + }, + { + "cache-control": "max-age=29030400, public" + }, + { + "content-length": "1140" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 39, + "wire": "88d66c96df697e94034a6e2d6a080112810dc6deb82654c5a37fefdedd5893aed8e8313e94a47e561cc581c136db22780fff6496d07abe94640a436cca080c89408ae043702f298b46ffc50f0d03363536d5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 04 Sep 2012 11:58:23 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=625532809" + }, + { + "expires": "Mon, 30 Aug 2032 12:11:18 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:29 GMT" + }, + { + "content-length": "656" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 40, + "wire": "88d96c96d07abe940054d444a820044a04171966e002a62d1bfff2e1e05893aed8e8313e94a47e561cc581c13cdb400bafff6496d07abe94034a6a22541019128076e32d5c03ca62d1bfc80f0d83109b0fd8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Mon, 01 Oct 2012 10:33:01 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=628540179" + }, + { + "expires": "Mon, 04 Oct 2032 07:34:08 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:29 GMT" + }, + { + "content-length": "2251" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 41, + "wire": "88dc6c96c361be941054dc5ad410022500fdc6dbb810298b46fff5e4e35893aed8e8313e94a47e561cc581c13a2704f3ccff6496dd6d5f4a05f53716b5040644a04571a6ee36253168dfcb0f0d83081f7bdb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 21 Sep 2012 09:55:10 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=627262883" + }, + { + "expires": "Sun, 19 Sep 2032 12:45:52 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:29 GMT" + }, + { + "content-length": "1098" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 42, + "wire": "88df6c96e4593e94642a6a2254100225021b8d3d71b7d4c5a37ff8e7e65893aed8e8313e94a47e561cc581c640d81c034e7f6496e4593e9413aa6a2254101912800dc65eb8cb6a62d1bfce0f0d830b2d0bde", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 31 Oct 2012 11:48:59 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=630506046" + }, + { + "expires": "Wed, 27 Oct 2032 01:38:35 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:29 GMT" + }, + { + "content-length": "1342" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 43, + "wire": "88e20f0d8308401f6c96d07abe9403ea65b68504008940b971a05c0baa62d1bfe55892aed8e8313e94a47e561cc581c105c6c2c89e6497c361be940b8a65b685040644a059b8dbf71b754c5a37ffd1e1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "content-length": "1101" + }, + { + "last-modified": "Mon, 09 Jul 2012 16:40:17 GMT" + }, + { + "content-type": "image/png" + }, + { + "cache-control": "public, max-age=621651328" + }, + { + "expires": "Fri, 16 Jul 2032 13:59:57 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 44, + "wire": "885f88352398ac74acb37f0f0d8313426be26196df3dbf4a05e535112a080112817ae36e5c0baa62d1bfe7e1e06c96c361be9403aa6a225410021500cdc659b8dbca62d1bf55850b2e044fb37f17add1b6bddb9cbbaf49ba7ae23abb4db5ca7d499bce2ae7c9666a1fe9be926298df82bf49394dc64c3e37ec178820dcdb", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2424" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 18 Oct 2012 18:56:17 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 07 Oct 2011 03:33:58 GMT" + }, + { + "age": "1361293" + }, + { + "x-amz-cf-id": "MRpSS6BPNijyVanqgR6mydKxGphIrKl9jTmcGgiX2DmcWgVdFwTQ2w==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 45, + "wire": "88c20f0d8365e00be66196c361be940bea6a225410022500f5c65ab82754c5a37febe5e46c96df697e94642a651d4a0801128266e361b800a98b46ff55850b211080cf7f02ac3ce81adc63c1a22a7711ec15570e0fcc09e0e69eadbefcd8bcf516148f895fd99bbd07ed1f8dd7a3f3a61820e0df", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3802" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 19 Oct 2012 08:34:27 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 31 Jan 2012 23:51:01 GMT" + }, + { + "age": "1312203" + }, + { + "x-amz-cf-id": "ohsa-VbEM_mSc8EnpAEXEtU6Nk599gGxk2FtaVe9QKvloqbwSCbxNA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 46, + "wire": "885f8b497ca58e83ee3412c3569f0f0d0132db4087aaa21ca4498f57842507417ff0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/javascript" + }, + { + "content-length": "2" + }, + { + "date": "Sat, 03 Nov 2012 13:04:29 GMT" + }, + { + "nncoection": "close" + }, + { + "server": "Server" + } + ] + }, + { + "seqno": 47, + "wire": "886196dc34fd280654d27eea0801128166e01ab8c814c5a37ff17f1d9206dd1972fe62de1cdc7068fcfc678eecbaff4003703370ff12acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7eaf6b83f9bd0ea52feed6a67879297b86d521bfa14c9c613a9938df3a97b5693a9ab7eb3a9ab86d52fe0ce6535f0ba65356fda652ef0dca6bc7cd4d5a73a9c34e4535f0daa61c9a54bdab429a61e2a64d3bd4bf834297b4ef5376f854c7821535edc0a67d5794c5ab8a9ab7de53f97f1db5d3db169c11b1ba5ebe73e4963d5ddd6676f2ddba4cfc74e2e98b3e67f15ed397c4cdefe42b323d7fcdbf891a3dd99dbe1fb3efe7fb7b9384842d695b05443c86aa6fae082d8b43316a4f5a839bd9ab5f87352398ac4c697f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dff798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "0RMJJXGT1KVEMXX3VSJP" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "NqGNEb/SfkxLIfbOv73h5JBBcLVNGjGLK9GCNJwg5TW2rI8DxuXtaszrL5UZhTYZ" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 48, + "wire": "88c57685dc5b3b96cf0f28ba4753550547475355f6a5634cf031f6a487a466aa05c748fd9ea5c87a7ed42f9acd615106e1a7e94032b693f7584008940b3700d5c138a62d1bff4085aec1cd48ff86a8eb10649cbf7f079108b47eede6cef85ffc7af5fe78eef16f7fc65886a8eb10649cbfe67f07b4a6845193463aff47e34e1462c20f21cfc7076b36f1c4c93aeae9fef5b3b76efcf4f8777cee1f3e475bc3d36e636823f454c0a8cdc6c55f96497ca589d34d1f6a1271d882a60c9bb52cf3cdbeb07f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dffc4408cf2b0e9f752d617b5a5424d279a96591b132d32b09b8dc582128961902eacdbae3600b323aebedf5892a47e561cc5804f819034007d295db1d0627f0f0d83085a074084f2b563938d1f739a4523b0fe105b148ed9bf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "server": "Server" + }, + { + "set-cookie": "skin=noskin; path=/; domain=.amazon.com; expires=Sat, 03-Nov-2012 13:04:26 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-amz-id-1": "12MZRY3TA9X8CDYHBV5T" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "x-amz-id-2": "mlslIMHpZawNFsGF0x1LVEqrRVG3ckOj+P3RRTLmw7Th6oLI75FjRKiMc9ln/2lK" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html; charset=ISO-8859-1" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "x-amzn-requestid": "ffd52343-25b6-11e2-ac17-5765013d7795" + }, + { + "cache-control": "max-age=29030400, public" + }, + { + "content-length": "1140" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 49, + "wire": "88d90f0d8413cfbcf7408721eaa8a4498f5788ea52d6b0e83772ff6196dc34fd282029a8895040089403d702cdc136a62d1bffc85891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96d07abe94132a65b6850400894102e32ddc6dd53168df55850884e81c6f7f18aba74e44ad505ccf7b2c867a92604d877ff389614e2d3bc45b51ab66747e7510ee9e18b3e2a9f35b8a3d90417caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "28988" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 20 Oct 2012 08:13:25 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 23 Jul 2012 20:35:57 GMT" + }, + { + "age": "1227065" + }, + { + "x-amz-cf-id": "mNIt-n16LCJdi8mcEtro9XVeAtGNT2eusOQLsXk2aBoA_LGn9iuGbQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 50, + "wire": "88e20f0d846402643fc66196df3dbf4a002a693f75040089403971915c65953168dfd0c5c46c96df3dbf4a002a693f750400894002e00171b7d4c5a37f55850bee32177f7f04aec392fc6fe1bdb356f1d6cd6d6571cdf4599247bc64fd8b2038787afcd9d9cd8bd6bc1a5bc3926edfad5dd75c3041c3c2408725453d44498f57842507417f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "30231" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 06:32:33 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 00:00:59 GMT" + }, + { + "age": "196317" + }, + { + "x-amz-cf-id": "FIDb9FCQOTap3p4J66TlrId8wIZ_I0Uw8DgL3KGyPEN5FIgqZ4BPpA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 51, + "wire": "88d60f0d84136e3ef7cb6196c361be9413ca6e2d6a0801128105c65fb8c894c5a37fd56c96e4593e940bea6e2d6a0801128176e045704f298b46ffcbca558564217c0fbd7f03ac9f366a0de7618c0ef446f8e2be6bf7eb9f9b39f0703e4ee26bb3fc65bccf0d49aeacbc5976dcfcb8574f8820c8c7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "25698" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 28 Sep 2012 10:39:32 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 19 Sep 2012 17:12:28 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "3119098" + }, + { + "x-amz-cf-id": "hKKlixQii0vlb9a_DiDDphY3LEUoIv24q9VfC3UOtpnJV37uLWUpmw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 52, + "wire": "88eb0f0d84138c843fcf6196e4593e94642a6a225410022502e5c6dcb8d094c5a37fd9cecd6c96d07abe940b6a6a2254100225040b8166e32053168dff5585134d89c7bf7f02aba2be010e4d81e913a7a5e8b0f1c99d1eff437b07159ef70cc17459455cef5088fb2599ec972d5b9d2ec820cccb", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "26311" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 16:56:42 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 15 Oct 2012 20:13:30 GMT" + }, + { + "age": "245268" + }, + { + "x-amz-cf-id": "lpU11IQ1j_7om8_FVILszZ1CEV-8zAg172J2ph8lsbqt3hrfJnS7eQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 53, + "wire": "88ef0f0d8465a6de73d36196df3dbf4a09b535112a080112820dc65ab807d4c5a37fddd2d16c96d07abe941094d444a820044a05cb8cb3702d298b46ff558575a74020ff7f02adbfa631a7d20b57e0b3f0e0e79eb6ea668c44f33dfe3f82bf89bbdefb71e15e7c7afc2d9d62eeda9a9d8bbf8820d0cf", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "34586" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 25 Oct 2012 21:34:09 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 16:33:14 GMT" + }, + { + "age": "747021" + }, + { + "x-amz-cf-id": "DNbatysenX2LUU6xkuO3lGcxhDVX2DG5CzqVUpLHPw-L-eSRtn7_vw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 54, + "wire": "88e87f1b9a005a232cba0584dc6eac10944b46d38359d64a21bcc85f70b27fe3e5e40f0d023533", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "x-amzn-requestid": "014c3370-25b7-11e2-b46a-73e2a83196ed" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "53" + } + ] + }, + { + "seqno": 55, + "wire": "88e9e1e8e7e6e5e45f89352398ac7958c43d5f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dffe36c96df697e941054dc5ad410020502edc65db8d054c5a37f0f138cfe5a69e716748d8dc65a07f352848fd24a8f0f0d83136f83", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "0RMJJXGT1KVEMXX3VSJP" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "NqGNEb/SfkxLIfbOv73h5JBBcLVNGjGLK9GCNJwg5TW2rI8DxuXtaszrL5UZhTYZ" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/x-icon" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "last-modified": "Tue, 21 Sep 2010 17:37:41 GMT" + }, + { + "etag": "\"4486-7c5a6340\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "2590" + } + ] + }, + { + "seqno": 56, + "wire": "885f87352398ac5754df0f0d8465b6da0fdc6196df697e94640a6a225410022500edc002e002a62d1bffe66c96c361be94134a436cca080112816ee05fb821298b46ffdcdb558565c75a71ff7f07abeb5eebaf43d1a40b6121dce39b851bb4dd5bc517a5f14c9979509bc09408d6441b9f72608cc9bb59f64107d9d8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "35541" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 30 Oct 2012 07:00:01 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 24 Aug 2012 15:19:22 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "367469" + }, + { + "x-amz-cf-id": "kpSB8Aj4s2QcAS66S2b7mB-wlCfwmdJWltC0f0sPcsiYvcEbitBpoQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 57, + "wire": "88c20f0d84642cb4cfe06196c361be94138a6a2254100225041b8176e34053168dffeadfde6c96e4593e94134a6a225410022502f5c13571a6d4c5a37f558571c0b8107f7f02ad3337b7fa9dc4539376be3b1dcfdb2f06f5ec6f670f18e4d76fe9b723c4d9a7a826db668daeb3e5be7b26a26820dddc", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "31343" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 26 Oct 2012 21:17:40 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 24 Oct 2012 18:24:45 GMT" + }, + { + "age": "661610" + }, + { + "x-amz-cf-id": "i3CTyh6smISPVQ7LqJU5PQ5QUwHdPuZiSswgKhn1iRrMR73x5YQglg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 58, + "wire": "88c60f0d84640f85ffe46196c361be940bea6a225410022502e5c643704ea98b46ffeee3e26c96df3dbf4a099521b665040089410ae043700153168dff5585089e65b7997f02aea5ea52f3866e7d583c861d15ed8cee76bbef0f9ed97b6fce5eaf7bf5cb0f3e9e7a0b4e1dee562f5b63abf7c4107fe1e0db", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "30919" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 19 Oct 2012 16:31:27 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 23 Aug 2012 22:11:01 GMT" + }, + { + "age": "1283583" + }, + { + "x-amz-cf-id": "m8mt86i5hOEx1AMpRbo6qBzFxqJqTLek8zyWFYjxj2NFT6p2yRbnZw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 59, + "wire": "88f16c96d07abe941094d444a820044a05bb8205c69953168dff5f911d75d0620d263d4c795ba0fb8d04b0d5a77b8b84842d695b05443c86aa6ff75892aed8e8313e94a47e561cc581c64020b2d3606496dc34fd282654d444a820322502e5c10ae000a62d1bff6196dc34fd280654d27eea0801128166e01ab8c814c5a37f0f0d8365f75eee", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Mon, 22 Oct 2012 15:20:43 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=630213450" + }, + { + "expires": "Sat, 23 Oct 2032 16:22:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "content-length": "3978" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 60, + "wire": "88f76c96dc34fd28012996da9410022502edc13571b0a98b46ff5f86497ca582211fc35a839bd9ab5893aed8e8313e94a47e561cc581c0bafbacb6077f6496c361be94034a65b6a5040644a0017042b8dbaa62d1bfc30f0d83132d8bf3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Sat, 02 Jun 2012 17:24:51 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=617973507" + }, + { + "expires": "Fri, 04 Jun 2032 00:22:57 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "content-length": "2352" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 61, + "wire": "887685dc5b3b96cf6c96c361be9413ca6e2d6a0801128076e01bb8cb2a62d1bfc3c8c25892aed8e8313e94a47e561cc581c13c203421356496df3dbf4a32053716b5040644a041702d5c6da53168dfc70f0d83105f17f7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 28 Sep 2012 07:05:33 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=628204224" + }, + { + "expires": "Thu, 30 Sep 2032 10:14:54 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "content-length": "2192" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 62, + "wire": "88c16c96d07abe9413ea6a2254100225000b8c86e05f53168dffc6cbc55893aed8e8313e94a47e561cc581c640d36db826bf6496df697e94138a6a2254101912810dc65eb81694c5a37fca0f0d830baf33408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Mon, 29 Oct 2012 00:31:19 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=630455624" + }, + { + "expires": "Tue, 26 Oct 2032 11:38:14 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "content-length": "1783" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 63, + "wire": "88c50f0d8365d6856c96e4593e940b2a65b6a504008940bf7197ee36fa98b46fca5893aed8e8313e94a47e561cc581c0bccbcf36fb9f6496df697e9403ca65b6a5040644a05fb8d06e01c53168dfcec1d1cb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "content-length": "3742" + }, + { + "last-modified": "Wed, 13 Jun 2012 19:39:59 GMT" + }, + { + "content-type": "text/css" + }, + { + "cache-control": "public, max-age=618388596" + }, + { + "expires": "Tue, 08 Jun 2032 19:41:06 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 64, + "wire": "88e00f0d03333539c16196d07abe94132a651d4a080112817ae34edc6de53168dfc95891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96c361be94089486d99410021502cdc699b8d32a62d1bf558410197c1f7f1aaecc19ef72a7cbbb4b77ca3f1b6ff2d87af4c7f98014ed465e991b05ba73606f170bbd9add2616b7fbf3a565a1820f7caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "359" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 23 Jan 2012 18:47:58 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 12 Aug 2011 13:43:43 GMT" + }, + { + "age": "20390" + }, + { + "x-amz-cf-id": "K1hCWmx7ReBxsX55XuAkjHXE0mRsJjI50uNKE5GUBq4SdF4TzxN--A==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 65, + "wire": "48826402d7d158b1a47e561cc5801f4a547588324e5fa52a3ac849ec2fd295d86ee3497e94a6d4256b0bdc741a41a4bf4a216a47e47316007f4085aec1cd48ff86a8eb10649cbf6496df3dbf4a002a651d4a05f740a0017000b800298b46ff4003703370c1acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7e94bdae0fe75ee84ea6bdd7cea6ae1b54dd0e85356fdaa5fddad4bdab6ff30f1fed9d29aee30c2171d23f67a961c88f4849695c87a5835acff92403a47ecf52e43d3f030c1f0314000802565f95d6c637d969c6ca57840138f3856656c51904eb4dc641ca2948db6524b204a32b8d3a171d1bcc9491c6dfc1e892239e007c5500596c2fb4ebce81e71bf89084813f4087aaa21ca4498f57842507417f0f28d11c8b1a4821775f3a7d0a5ba0edfb561f13d2644983ce8ff89fb52f9e919aa8171d23f67a961c88f4849695c87a7ed4c1e6b3585441be7b7e940056ca3a961019754002e001700153168dff6a6b1a67818f7b9384842d695b05443c86aa6fae082d8b43316a4fda0f0d0232305f87497ca58ae819aa", + "headers": [ + { + ":status": "302" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\", CP=\"PSAo PSDo OUR SAM OTR DSP COR\"" + }, + { + "location": "http://s.amazon-adsystem.com/iu3?d=amazon.com&a1=&a2=0101e39f75aa93465ee8202686e3f52bc2745bcaf2fc55ecfd1eae647167a83ecbb5&old_oo=0&n=1351947870865&dcc=t" + }, + { + "nncoection": "close" + }, + { + "set-cookie": "ad-id=A7PYmy2fB0qZnFwhmisdExM|t; Domain=.amazon-adsystem.com; Expires=Thu, 01-Jan-2037 00:00:01 GMT; Path=/" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "20" + }, + { + "content-type": "text/plain" + } + ] + }, + { + "seqno": 66, + "wire": "88ded8c4c3c2c10f1fed9d29aee30c2171d23f67a961c88f4849695c87a5835acff92403a47ecf52e43d3f030c1f0314000802565f95d6c637d969c6ca57840138f3856656c51904eb4dc641ca2948db6524b204a32b8d3a171d1bcc9491c6dfc1e892239e007c5500596c2fb4ebce81e71bf89084813fc00f28c11c8b5761bb8c9ea007da97cf48cd540b8e91fb3d4b0e447a424b4ae43d3f6a60f359ac2a20df3dbf4a002b651d4b080cbaa0017000b800a98b46ffb5358d33c0c7bfdb0f0d82105d5f95497ca589d34d1f649c7620a98326ed4b3cf36fac1f408725453d44498f57842507417f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\", CP=\"PSAo PSDo OUR SAM OTR DSP COR\"" + }, + { + "location": "http://s.amazon-adsystem.com/iu3?d=amazon.com&a1=&a2=0101e39f75aa93465ee8202686e3f52bc2745bcaf2fc55ecfd1eae647167a83ecbb5&old_oo=0&n=1351947870865&dcc=t" + }, + { + "nncoection": "close" + }, + { + "set-cookie": "ad-privacy=0; Domain=.amazon-adsystem.com; Expires=Thu, 01-Jan-2037 00:00:01 GMT; Path=/" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "217" + }, + { + "content-type": "text/html;charset=ISO-8859-1" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 67, + "wire": "88e0dac6c5c4c30f1fed9d29aee30c2171d23f67a961c88f4849695c87a5835acff92403a47ecf52e43d3f030c1f0314000802565f95d6c637d969c6ca57840138f3856656c51904eb4dc641ca2948db6524b204a32b8d3a171d1bcc9491c6dfc1e892239e007c5500596c2fb4ebce81e71bf89084813fc20f28c11c8b5761bb8c9ea007da97cf48cd540b8e91fb3d4b0e447a424b4ae43d3f6a60f359ac2a20df3dbf4a002b651d4b080cbaa0017000b800a98b46ffb5358d33c0c7c1dd0f0d03313538bfbe", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\", CP=\"PSAo PSDo OUR SAM OTR DSP COR\"" + }, + { + "location": "http://s.amazon-adsystem.com/iu3?d=amazon.com&a1=&a2=0101e39f75aa93465ee8202686e3f52bc2745bcaf2fc55ecfd1eae647167a83ecbb5&old_oo=0&n=1351947870865&dcc=t" + }, + { + "nncoection": "close" + }, + { + "set-cookie": "ad-privacy=0; Domain=.amazon-adsystem.com; Expires=Thu, 01-Jan-2037 00:00:01 GMT; Path=/" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "158" + }, + { + "content-type": "text/html;charset=ISO-8859-1" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 68, + "wire": "88e0dabfc2c1dd0f0d820b80", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "server": "Server" + }, + { + "content-type": "text/html;charset=ISO-8859-1" + }, + { + "nncoection": "close" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "160" + } + ] + }, + { + "seqno": 69, + "wire": "885886a8eb2127b0bf0f0d0234325f87352398ac4c697f5d9a9d29aee30c22b2ae34c94a5721e960d48e62a18acde4b42f31a56401307f08c9bdae0fe74eac8a5fddad4bdab6a97b86d521bfa14bf838a9be1c87535ee84ea6bdd7cea6ae1b54bbc3729c34e4535f0daa5ed5a14d30f153269dea5fc1a14ddbe1535edc0a6adf7bf90f28d0d1c325f8197af5f79e089c7b0dd704f6066fb217af070b9770dd704f3ff6a17cd66b0a88341ea907ebe94032b693f758400b4a0017000b800298b46ffb52b1a67818fb5243d2335502e34c94a5721e9fe57f19842507417f", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store" + }, + { + "content-length": "42" + }, + { + "content-type": "image/gif" + }, + { + "content-location": "http://spe.atdmt.com/images/pixel.gif" + }, + { + "expires": "0" + }, + { + "p3p": "CP=\"NOI DSP COR CUR ADM DEV TAIo PSAo PSDo OUR BUS UNI PUR COM NAV INT DEM STA PRE OTC\"" + }, + { + "set-cookie": "MUID=38CD881268FB628E3D318C1F6BFB6289; expires=Monday, 03-Nov-2014 00:00:00 GMT; path=/; domain=.atdmt.com" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "connection": "close" + } + ] + }, + { + "seqno": 70, + "wire": "887689bf7b3e65a193777b3feb0f0d03333133e46196dc34fd280654d27eea0801128166e01ab8c854c5a37f", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "313" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + } + ] + }, + { + "seqno": 71, + "wire": "88c50f0d023432c4c3c2c10f28d0d1c325f8197ef05e699bb7ddbd75c75fc00704cbc065cbed5ebae3a0bbf6a17cd66b0a88341ea907ebe94032b693f758400b4a0017000b800298b46ffb52b1a67818fb5243d2335502e34c94a5721e9fe8c0", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store" + }, + { + "content-length": "42" + }, + { + "content-type": "image/gif" + }, + { + "content-location": "http://spe.atdmt.com/images/pixel.gif" + }, + { + "expires": "0" + }, + { + "p3p": "CP=\"NOI DSP COR CUR ADM DEV TAIo PSAo PSDo OUR BUS UNI PUR COM NAV INT DEM STA PRE OTC\"" + }, + { + "set-cookie": "MUID=39C1843BD7CB679E06238036D4CB670B; expires=Monday, 03-Nov-2014 00:00:00 GMT; path=/; domain=.atdmt.com" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "connection": "close" + } + ] + }, + { + "seqno": 72, + "wire": "885f8b497ca58e83ee3412c3569f0f0d8371f6c3e9e3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/javascript" + }, + { + "content-length": "6951" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "server": "Server" + } + ] + }, + { + "seqno": 73, + "wire": "8bc50f0d840baf34ffdc4088f2b0e9f6b1a4585fb5fd9a4dbea4efe788bf5be7ae5a1fef3fefeefe7cb46afc77bd0f77c897f6dba37d1489b7b1c7ce6f21f5a27c68fc371dfb5bde1f1f408cf2b0e9f6b585ed6950958d278c0b2d85e15d6c2e05ebe27ddfc16c96e4593e940bca65b685040089410ae34cdc138a62d1bf0f1399fe40f46095975f68a18db2be37c4eb8fbc50b6fbad048d83f952848fd24a8f76868691fb3d5b9955846c0e38ff7f1aade1762a2eb66e8971d7b60bc6566ee55b8444e3dd3baae50f19786ffc25ecd3ceb6c867c9b1fbb7a3664e9b20837caf0ae0508cb2592395b69b7c6d0da69d69f20891b652491b32906b9283db24b61ea4af5152a7f57a83db261b0f527fbfd9", + "headers": [ + { + ":status": "304" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "17849" + }, + { + "connection": "keep-alive" + }, + { + "x-amz-id-2": "ZgdRydvxV2Z5YPfl9vhZZTYWMOX7vl8vIt9RuMTlm258HbYgx1yMhHsXiVTR5T1w" + }, + { + "x-amz-request-id": "135182B51618D297" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + }, + { + "last-modified": "Wed, 18 Jul 2012 22:43:26 GMT" + }, + { + "etag": "\"08b0f3794e1b5e9a927698e159741c50\"" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "AmazonS3" + }, + { + "age": "50669" + }, + { + "x-amz-cf-id": "UB_lB5ijt678Q2wJ3BJ-U_cVvtSnWAVfUTXcCKhh-QAhIQ9BCb3djQ==" + }, + { + "via": "1.0 c33edbf5459a4a44749c2cb5ecdb3fca.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 74, + "wire": "88c7408cf2b0e9f752d617b5a5424d27990046f3cc900b09b8dd582128961902558afbb23c169d7c8f37ced3ef0f0d023533", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + }, + { + "x-amzn-requestid": "01a883c0-25b7-11e2-ac1e-e97d81479c85" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "53" + } + ] + }, + { + "seqno": 75, + "wire": "88c8ec0f28ba4753550547475355f6a5634cf031f6a487a466aa05c748fd9ea5c87a7ed42f9acd615106e1a7e94032b693f7584008940b3700d5c138a62d1bffd74088f2b0e9f6b1a4583f90037af90316f007672d6d35830e6c20c17f0dff12acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7eaf6b83f9bd0ea52feed6a67879297b86d521bfa14c9c613a9938df3a97b5693a9ab7eb3a9ab86d52fe0ce6535f0ba65356fda652ef0dca6bc7cd4d5a73a9c34e4535f0daa61c9a54bdab429a61e2a64d3bd4bf834297b4ef5376f854c7821535edc0a67d5794c5ab8a9ab7de53f95886a8eb10649cbfcf7f0ab2c57a73b14e5d57db0eab65374ede1c9bb5738580a321a1bd8af66cbf99a717baf40eff472d6e684334dcad4111c9919fa64fd7f3d20f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dff798624f6d5d4b27f7f049a96591b132d32b09b8dc582128961902eacdbae3600b323aebedf5892a47e561cc5804f819034007d295db1d0627f0f0d83085a074084f2b563938d1f739a4523b0fe105b148ed9bf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + }, + { + "server": "Server" + }, + { + "set-cookie": "skin=noskin; path=/; domain=.amazon.com; expires=Sat, 03-Nov-2012 13:04:26 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-amz-id-1": "05PW0GT01QWP44EFKF0E" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "x-amz-id-2": "GCho/mJOD51Oufijqw6gqph1/1sIiACGCKJXKh2zpMaDj6u5gA1ggWuscsW3aojI" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "x-amzn-requestid": "ffd52343-25b6-11e2-ac17-5765013d7795" + }, + { + "cache-control": "max-age=29030400, public" + }, + { + "content-length": "1140" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 76, + "wire": "885f87497ca589d34d1f0f0d03313837ee7f04b41ffb468772d5983edefccb0e7c6eb0c38c6bb3f327aedb936bdabf2bc3e789c7f8e4c3e5c25fdc37da770d82d9b380d896b761c97f108c0b2f3cfbede0bd79b03216c36196d07abe941094d444a820044a05db8272e36ca98b46ff409ff2b0e9f6b52548d6e854a194ac7b0d31aa1d0b4a6a0ab4834956320ef3800f92100215821580eef106e32cdc69a5c0007eff408ef2b0e9f6b52548d6a646d69c689f961881246d3201800103f23c00bcd36e80a4146dc8cbc5588aa47e561cc581e71a00016c96df697e9403ca693f7504008540bd71976e042a62d1bf0f1399fe4620491b4c80600040fc8f002f34dba029051b7232f17f9fd3d255850b6e81b7ff7f12acce873233ee7cfb7c2de8199e1e4e15f532df8334b9bd70bb928edd336d4c23d99021e7e02cb7d217a19e68207caf0ae0500e46cbd18a49091c6d36478acb3246170231321702d3eb9283db24b61ea4af5152a7f57a83db261b0f527fbfed", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/html" + }, + { + "content-length": "187" + }, + { + "connection": "keep-alive" + }, + { + "x-amz-id-2": "a+sM7JnK1z8XJALH7//6/PrXIyqStu8OXpFxVoaX6gaWUfZFD47Fr2QQUa/fp7AI" + }, + { + "x-amz-request-id": "1388995ECC503151" + }, + { + "date": "Mon, 22 Oct 2012 17:26:53 GMT" + }, + { + "x-amz-meta-jets3t-original-file-date-iso8601": "2011-11-07T21:33:44.000Z" + }, + { + "x-amz-meta-md5-hash": "a20db430a00109d80184570ec2b5d38e" + }, + { + "cache-control": "max-age=864000" + }, + { + "last-modified": "Tue, 08 Nov 2011 18:37:11 GMT" + }, + { + "etag": "\"a20db430a00109d80184570ec2b5d38e\"" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "AmazonS3" + }, + { + "age": "157059" + }, + { + "x-amz-cf-id": "Ls6I3zhLRw-y0K8aIUpki-XaifKyUBIlqjKRtAaQI11Yw135jA8Ahg==" + }, + { + "via": "1.0 06b38b2ddcbb45c8e33db161a2316149.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 77, + "wire": "88c80f0d8213227f1e88ea52d6b0e83772ff7f09b4e8e0b847e3acf3762b0e63e1479f4e43361c38591be93dfff7bf5b7ab13499ecf5c3d7786dc9b7ae0d7fbeeade4c1d514105dedf7f098dbe2684fb97ef32d33819bed5ffc87f089210022580f2cc83785eb800dc69c5c0007eff7f0897191f1332c6e371b2e46e95c9250c6f36591f188a58c4dfc76c96df3dbf4a320521b66504008940bd7002b82654c5a37f0f139afe4647c4ccb1b8dc6cb91ba57249431bcd9647c622963137fcffdcdbc67f06adb5ac7e6429b5f417eb8fd1b1d1f5f4f6bf1b598f2670dfc82cffbdcd7acc0692f17369dbf993deee006b1f8820c5f4", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/html" + }, + { + "content-length": "232" + }, + { + "connection": "keep-alive" + }, + { + "x-amz-id-2": "j62Ubwkhgqe/6HUlxy6AgFFF3a9toD+TP5OG4thryUyvAuIRkEPZznTcEkslc2vu" + }, + { + "x-amz-request-id": "D24296DC343E3D4D" + }, + { + "date": "Mon, 22 Oct 2012 17:26:53 GMT" + }, + { + "x-amz-meta-jets3t-original-file-date-iso8601": "2012-08-30T18:01:46.000Z" + }, + { + "x-amz-meta-md5-hash": "ac923fb65b36b7e6df1b85ed9a2eeb25" + }, + { + "cache-control": "max-age=864000" + }, + { + "last-modified": "Thu, 30 Aug 2012 18:02:23 GMT" + }, + { + "etag": "\"ac923fb65b36b7e6df1b85ed9a2eeb25\"" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "AmazonS3" + }, + { + "age": "157059" + }, + { + "x-amz-cf-id": "u4HxdeiPj2Z69lQ7aky8PwR3bIL1DI2LZviCrEidCeKNRXIzSU04Hw==" + }, + { + "via": "1.0 06b38b2ddcbb45c8e33db161a2316149.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 78, + "wire": "88cf0f0d03323436c47f04b485eaf5170eff5ef952eb93fe70730cb9faec95a23fe3f6f6a943b30bfb5adf8baf4fcc192ad996b7251d9d7acc0e57418e5f04cd7f048c6ae819142fb8cbb7dd0bd7dbce7f0492100215821580f6f0bd7197ae32eae0003f7f7f04971c71802f3318c6028df7db8da764210057df74407db1ffcd6c96df697e9403ca693f7504008540bd71a0dc0094c5a37f0f1399fe471c600bccc63180a37df6e369d9084015f7dd101f6c7fcfe2e1cc7f04ac38051e5e5c22e9bb038e64f19f761433daaae4873ecdb313036ced2d90830e919e7ab2563f586dc97794d041cb4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/html" + }, + { + "content-length": "246" + }, + { + "connection": "keep-alive" + }, + { + "x-amz-id-2": "A8pOeFTyzWm76hXU6FfLkQf4c9wZCOf1QF9R4TGkjXEInQJp6farkkg0WB0HfwcK" + }, + { + "x-amz-request-id": "4B032A9637D718D5" + }, + { + "date": "Mon, 22 Oct 2012 17:26:53 GMT" + }, + { + "x-amz-meta-jets3t-original-file-date-iso8601": "2011-11-08T18:38:37.000Z" + }, + { + "x-amz-meta-md5-hash": "abb0183baa0ea995b47dcc0e9972095a" + }, + { + "cache-control": "max-age=864000" + }, + { + "last-modified": "Tue, 08 Nov 2011 18:41:02 GMT" + }, + { + "etag": "\"abb0183baa0ea995b47dcc0e9972095a\"" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "AmazonS3" + }, + { + "age": "157059" + }, + { + "x-amz-cf-id": "o02bJWU_jSE66IwLSFs3qnpdALQRgcE53RerA0FNaohnIpayFuIBWg==" + }, + { + "via": "1.0 06b38b2ddcbb45c8e33db161a2316149.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 79, + "wire": "88d60f0d82109dcb7f05b67f3e5f6edffcd5978f60f7ff6e74d9775f2ff7af343a7d7fb7b85db3aa76f0af73744923b538d4ba1bba093befc1c7ab27cdaa11ece97f058d77004583032f5fbcddfc0ce1076196c361be94138a6a2254100225040b8db771b0a98b46ff7f069110022580d2c10ef0bd71b7ee002b8000fd7f06972b4dc6f142075a96371b2b4d3b23923c37c25965786fb5d56c96e4593e94085486bb1410022502f5c6dfb8d32a62d1bf0f139afe4ad371bc5081d6a58dc6cad34ec8e48f0df096595e1bed7f3feae9558571c13e20ff7f07aebae6977bb5fb9fdd987d71dbc7ce924fbfd71d42b6ad5ef2e3c99997fb3dbb2990e91caf959935786acbc1d9041fd4c6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/html" + }, + { + "content-length": "227" + }, + { + "connection": "keep-alive" + }, + { + "x-amz-id-2": "9LJz7DXOJVq1v+6jQBPW+PKANy+8UBrktRUpS5ldd7n64fM5B0dvTEVk3oKOAaQj" + }, + { + "x-amz-request-id": "7E12EE38DC5DE3F0" + }, + { + "date": "Fri, 26 Oct 2012 20:55:51 GMT" + }, + { + "x-amz-meta-jets3t-original-file-date-iso8601": "2012-04-11T18:59:01.000Z" + }, + { + "x-amz-meta-md5-hash": "e45b8e1074fb65e447d6d8a91eff8a94" + }, + { + "cache-control": "max-age=864000" + }, + { + "last-modified": "Wed, 11 Apr 2012 18:59:43 GMT" + }, + { + "etag": "\"e45b8e1074fb65e447d6d8a91eff8a94\"" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "AmazonS3" + }, + { + "age": "662921" + }, + { + "x-amz-cf-id": "B6N7v4ZLzrFyVRVxNchTyVO2unOzJHIK39q8SJis7c6pWrIOw4rC1Q==" + }, + { + "via": "1.0 06b38b2ddcbb45c8e33db161a2316149.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 80, + "wire": "88de0f0d03323336d37f06b5677f125b6b9a363ce969ff78a4c9fedff627688eff9fce0d99fc6ffefb7f1648e9659bbfa77fbeb9b25e621ea6a2ee66d385d6f67f7f068dc21036e3cfbaf86f3d7b0de73fc57f059210022580dac17b785eb8d39700d2e0003f7f7f05978c2175e91a96368210c81036f0c2f3a4959091f6c71bffdc6c96c361be940bca681fa504008940bd71a76e042a62d1bf0f1399fe63085d7a46a58da08432040dbc30bce92564247db1c6fff352848fd24a8f76868691fb3d5b99c67f06ad466d73f3e1e87e36d9979552f247ac59c48649d6ed471d79767397cf7ac3c99cdc7378d06e13f5c199b0f8820fdcce", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/html" + }, + { + "content-length": "236" + }, + { + "connection": "keep-alive" + }, + { + "x-amz-id-2": "3TVcuu6MQ87em+GdI+9z27lbDxXU5i9H9Zz9GIbm33BZo9vPgIC/AkilBK5tF75Q" + }, + { + "x-amz-request-id": "F105689791C8CFC6" + }, + { + "date": "Fri, 26 Oct 2012 20:55:51 GMT" + }, + { + "x-amz-meta-jets3t-original-file-date-iso8601": "2012-05-18T18:46:04.000Z" + }, + { + "x-amz-meta-md5-hash": "b1178d4fb4111d1058a187cf31c95ab9" + }, + { + "cache-control": "max-age=864000" + }, + { + "last-modified": "Fri, 18 May 2012 18:47:11 GMT" + }, + { + "etag": "\"b1178d4fb4111d1058a187cf31c95ab9\"" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "AmazonS3" + }, + { + "age": "662921" + }, + { + "x-amz-cf-id": "sKPhYUyawRrJWnfWsyGL2s3ckBnoapJQYfxvp1W3KVKwMiUhkEK51w==" + }, + { + "via": "1.0 06b38b2ddcbb45c8e33db161a2316149.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 81, + "wire": "488264025f92497ca589d34d1f6a1271d882a60b532acf7f7f3099bdae0fe6f70daa437f429ab86d534eadaa6edf0a9a725ffe7f7f1f842507417f0f1fbe9d29aee30c2171d23f67a961c88f4849695c87a58292967fc2f980f596af2b90f4fc1a481a00dc08652ac07257d6246eb4b09c1bcb3b1b659081101b2bbf0f0d01300f28c534048e42362906b46cc8d2cd4aebeb464740b3211064001c964947f6a772d8831ea803f6a5634cf031f6a487a466aa05cf596af2bd454fda948fcac398b038c81d10000fbf", + "headers": [ + { + ":status": "302" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "p3p": "CP=\"CUR ADM OUR NOR STA NID\"" + }, + { + "connection": "close" + }, + { + "location": "http://s.amazon-adsystem.com/ecm3?ex=openx.com&id=40a611fe-06f9-cb74-26a8-7b5edc1205e7" + }, + { + "content-length": "0" + }, + { + "set-cookie": "i=cbdc52da-b3d4-4f79-bc70-3121d006fdfa; version=1; path=/; domain=.openx.net; max-age=63072000;" + } + ] + }, + { + "seqno": 82, + "wire": "c16196dc34fd280654d27eea0801128166e01ab8c854c5a37f7690dfb75a90d6324e55af1fd1d25602b87f7f02afbdae0fe74eac8a5ee1b46a437f40d4bf8388d4df0e41a9ab86d52ef0dca64d37d4e1a72297b568534c3c54c9a77ff30f1fb79d29aee30c2171d23f67a961c88f4849695c87a58292967fc34907c17cc165b19887aabb0fd0a44ae43d3f0848d36a20a8eb5a82d8b1a40f0d01305885aec3771a4b0f28abdd836f1c1b725f83ed4c1e6b3585441be7b7e940056ca3a960bee814002e001700153168dff6a5634cf0314088ea52d6b0e83772ff8d49a929ed4c0dfd2948fcc020037f0488cc52d6b4341bb97f5f93497ca58ae819aafb50938ec4153070df8567bf", + "headers": [ + { + ":status": "302" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + }, + { + "server": "TRP Apache-Coyote/1.1" + }, + { + "p3p": "CP=\"NOI CURa ADMa DEVa TAIa OUR BUS IND UNI COM NAV INT\"" + }, + { + "location": "http://s.amazon-adsystem.com/ecm3?id=&ex=rubiconproject.com&status=no-user-id" + }, + { + "content-length": "0" + }, + { + "cache-control": "private" + }, + { + "set-cookie": "SERVERID=; Expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/" + }, + { + "keep-alive": "timeout=5, max=200" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "text/plain; charset=UTF-8" + } + ] + }, + { + "seqno": 83, + "wire": "885f87352398ac5754df0f0d84081f75ffe76196dd6d5f4a082a6a2254100225002b8d3571a6d4c5a37f7685dc5b3b96cf5891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96df697e94038a681d8a0801128215c102e09b53168dff5585085c032f397f11adc21bb550d65dcfe6f5ed6d4d92bb7f0b3022e8de2b69bf30b6243e2b07b3d5efbf9d78b25ee94f0dc5d134107f7caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbfe2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "10979" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 21 Oct 2012 02:44:45 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 06 Mar 2012 22:20:25 GMT" + }, + { + "age": "1160386" + }, + { + "x-amz-cf-id": "F1Bnl4JS9Kyz-O5cpuXeg0_j5GumDg2Qt1wp0zonzvxPGICjmUSeMg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 84, + "wire": "88c60f0d830b6273ef6196dd6d5f4a082a6a225410022500fdc002e044a62d1bffc56c96c361be940b6a65b68504008540bf71a15c65c53168dfc5c45585085975e6df7f03aecb8b3ed9aeb9469e83f37e49a6d31bf97aea6de8bbe7eb8e9e1b4bfaf358ba7acf3841871dee97ff43205bd9041fc2e6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "1526" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 21 Oct 2012 09:00:12 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 15 Jul 2011 19:42:36 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "1137859" + }, + { + "x-amz-cf-id": "JGLRgB6lNjaxDdggNb9JkO58_vLkHmUReZ84GjyLh10FHCjDZ1d15Q==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 85, + "wire": "88d1c858b1a47e561cc5801f4a547588324e5fa52a3ac849ec2fd295d86ee3497e94a6d4256b0bdc741a41a4bf4a216a47e47316007f4085aec1cd48ff86a8eb10649cbf6496df3dbf4a002a651d4a05f740a0017000b800298b46ff7f13c1acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7e94bdae0fe75ee84ea6bdd7cea6ae1b54dd0e85356fdaa5fddad4bdab6ff30f1fed9d29aee30c2171d23f67a961c88f4849695c87a5835acff92403a47ecf52e43d3f030c1f0314000802565f95d6c637d969c6ca57840138f3856656c51904eb4dc641ca2948db6524b204a32b8d3a171d1bcc9491c6dfc1e892239e007c5500596c2fb4ebce81e71bf89084813f4087aaa21ca4498f57842507417f0f28c11c8b5761bb8c9ea007da97cf48cd540b8e91fb3d4b0e447a424b4ae43d3f6a60f359ac2a20df3dbf4a002b651d4b080cbaa0017000b800a98b46ffb5358d33c0c77b9384842d695b05443c86aa6fae082d8b43316a4f5a839bd9ab0f0d0235375f87352398ac4c697f408725453d44498f57842507417f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\", CP=\"PSAo PSDo OUR SAM OTR DSP COR\"" + }, + { + "location": "http://s.amazon-adsystem.com/iu3?d=amazon.com&a1=&a2=0101e39f75aa93465ee8202686e3f52bc2745bcaf2fc55ecfd1eae647167a83ecbb5&old_oo=0&n=1351947870865&dcc=t" + }, + { + "nncoection": "close" + }, + { + "set-cookie": "ad-privacy=0; Domain=.amazon-adsystem.com; Expires=Thu, 01-Jan-2037 00:00:01 GMT; Path=/" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "57" + }, + { + "content-type": "image/gif" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 86, + "wire": "88dad1bfc2c1c00f0d023537c6c5c4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + }, + { + "server": "Server" + }, + { + "content-type": "image/gif" + }, + { + "nncoection": "close" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "57" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + } + ] + }, + { + "seqno": 87, + "wire": "88d16c96c361be94101486bb14100225020b806ee34053168dff5f911d75d0620d263d4c795ba0fb8d04b0d5a77b8b84842d695b05443c86aa6fc35892aed8e8313e94a47e561cc581c0b4e81d70426496df697e9413aa435d8a080c8940377021b8c894c5a37f6196dc34fd280654d27eea0801128166e01ab8c814c5a37f0f0d83081f6f7f1c88ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 20 Apr 2012 10:05:40 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=614707622" + }, + { + "expires": "Tue, 27 Apr 2032 05:11:32 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:30 GMT" + }, + { + "content-length": "1095" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 88, + "wire": "88da0f0d840842177fbe6196d07abe9413ea6a225410022502e5c6dcb8d814c5a37fd9d8d76c96d07abe94134a6e2d6a0801128205c69ab817d4c5a37f5585682f01c0ff7f12ad6cf15d3701ef86fb7473d88ccd64e2deff7db6364e5cf4ec8ea483ce7f1ecadf4dcb34d378306d2277e19a083fd64085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "11117" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 29 Oct 2012 16:56:50 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 24 Sep 2012 20:44:19 GMT" + }, + { + "age": "418061" + }, + { + "x-amz-cf-id": "5o_BiUaTAD5lYQsK4IV5TzqQ5cWYNQbnt0xLwze5jS-445EERctTFg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 89, + "wire": "885f88352398ac74acb37f0f0d84642d381fc46196e4593e940baa6a225410022502f5c13f704d298b46ffdf6c96df697e9403ea6a2254100225042b8d06e05b53168dffdfde55850b4d3ec81d7f04ad4f87d66b978d3775f324910aa6fc005bb777c39fdf3ddb2c775cc7e5570ba72f5efd22bd0f7cfc13ecc49a083fdcc3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "31461" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 17 Oct 2012 18:29:24 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 09 Oct 2012 22:41:15 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "1449307" + }, + { + "x-amz-cf-id": "tw9-4WwNBPYcd_2n5w02SSvFLzYSQr7PgoWnUBoekvj_CAvLUtzicg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 90, + "wire": "88e26c96df697e940054dc5ad41000fa820dc0b9704e298b46ffcecdd25893aed8e8313e94a47e561cc581c0bcdb606db7bf6496df3dbf4a040a65b6a5040644a05cb8d02e09f53168dfee0f0d8364416fcb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 01 Sep 2009 21:16:26 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=618550558" + }, + { + "expires": "Thu, 10 Jun 2032 16:40:29 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + }, + { + "content-length": "3215" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 91, + "wire": "88e56c96df3dbf4a080a6a225410021500f5c64171b7d4c5a37fd1d0d55892aed8e8313e94a47e561cc581c0b2c884f3206496dd6d5f4a042a435d8a080c8940357190dc682a62d1bff10f0d8365b685ce", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 20 Oct 2011 08:30:59 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=613322830" + }, + { + "expires": "Sun, 11 Apr 2032 04:31:41 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + }, + { + "content-length": "3542" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 92, + "wire": "88ea0f0d83101a6fce6196d07abe940b8a65b6850400894037704e5c65d53168dfe9e8e76c96e4593e940b2a65b6850400854106e360b8db4a62d1bf55867db642d3ad7f7f08adefd27a5ff69050d0fabf66f9483ec7bf8f1f6bdc7d74f4ddddeb0307e79cde81b2d0df7715bb3396df19b64107e6cd", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2045" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 16 Jul 2012 05:26:37 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 13 Jul 2011 21:50:54 GMT" + }, + { + "age": "9531474" + }, + { + "x-amz-cf-id": "vjhm9zt0l4ak9rTfcaqoDHHqCVyjy5BT-0EXxKy0Qu1D7GuQLeuwKQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 93, + "wire": "f90f1fcf9d29aee30c2171d23f67a961c88f4849695c87a58292967fc2f98243db1d0525062755ea2a7e2639e6a0b14c6920bd0e0dd82fd6e7bdf8aac1c7561fcde8e1bf2549a351fe2639e6a0b113b96c803ff5e06496c361be940054ca3a940bef814002e001700053168dff5892a8eb10649cbf4a536a12b585ee3a0d20d25f5f92497ca589d34d1f6a1271d882a60e1bf0acf7768abc73f53154d0349272d90f0d826420408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f", + "headers": [ + { + ":status": "302" + }, + { + "location": "http://s.amazon-adsystem.com/ecm3?ex=doubleclick.net&google_gid=CAESEDp6zTGnEVOFXTsUTIntlOo&google_cver=1" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "text/html; charset=UTF-8" + }, + { + "server": "Cookie Matcher" + }, + { + "content-length": "310" + }, + { + "x-xss-protection": "1; mode=block" + } + ] + }, + { + "seqno": 94, + "wire": "88f16c96c361be9413ea681fa504003ea08171b72e09d53168dfdddce15893aed8e8313e94a47e561cc581c0b2cb6e85c6bf6496dd6d5f4a042a435d8a080c8940b5700cdc6db53168df6196dc34fd280654d27eea0801128166e01ab8c854c5a37f0f0d03373339db", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 29 May 2009 20:56:27 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=613357164" + }, + { + "expires": "Sun, 11 Apr 2032 14:03:55 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + }, + { + "content-length": "739" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 95, + "wire": "88bef5eae9e8e70f1fed9d29aee30c2171d23f67a961c88f4849695c87a5835acff92403a47ecf52e43d3f030c1f0314000802565f95d6c637d969c6ca57840138f3856656c51904eb4dc641ca2948db6524b204a32b8d3a171d1bcc9491c6dfc1e892239e007c5500596c2fb4ebce81e71bf89084813fe60f28c11c8b5761bb8c9ea007da97cf48cd540b8e91fb3d4b0e447a424b4ae43d3f6a60f359ac2a20df3dbf4a002b651d4b080cbaa0017000b800a98b46ffb5358d33c0c7e5e40f0d023537e3e2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\", CP=\"PSAo PSDo OUR SAM OTR DSP COR\"" + }, + { + "location": "http://s.amazon-adsystem.com/iu3?d=amazon.com&a1=&a2=0101e39f75aa93465ee8202686e3f52bc2745bcaf2fc55ecfd1eae647167a83ecbb5&old_oo=0&n=1351947870865&dcc=t" + }, + { + "nncoection": "close" + }, + { + "set-cookie": "ad-privacy=0; Domain=.amazon-adsystem.com; Expires=Thu, 01-Jan-2037 00:00:01 GMT; Path=/" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "57" + }, + { + "content-type": "image/gif" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 96, + "wire": "88d50f0d84132f381fdb6196df697e9403ea6a225410022504cdc69fb8dbca62d1bff6f5f46c96df697e9403ea6a225410022504cdc133700ca98b46ff55851044113acf7f0bad70d35ef686fddfdb5cb4336edc181ee0f46ef04702e636afca2adadeaf12dfc2d6edb7fc68f76b550b1f9f1041f3da", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "23861" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 09 Oct 2012 23:49:58 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 09 Oct 2012 23:23:03 GMT" + }, + { + "age": "2121273" + }, + { + "x-amz-cf-id": "6igCzs5zDRpfl3uREE8U8b7UsUeKiOXlnR5OwfDF4SRDwMzu4n2Hxw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 97, + "wire": "886496dd6d5f4a01a5349fba820044a01cb8272e05a53168df6c96e4593e94036a6e2d6a080112820dc0bb702fa98b46ff0f1392fe42fb207c61585185b58ae371c8d901fcff588aa47e561cc5802e89e003769186b19272b025c4bb2a7f5b4b2298c69fef52848fd24a8fed7f31ff46bdae0fe74eac8a5fddad4bdab6a99e1e4a5ee1b5486fe83a97f0713a9be1c87535ee84ea6bdd7cea64e309d4c9c6f9d4c79371d4d5bf59d4d5c36a9ba1d0752ef0dca70d3914bdab429a61e2a64d3bd4bf834297b4ef5376f854d7b70299f55efe7e94bdae0fe74eac8a5fddad4bdab6a99e1e4a5ee1b5486fe83a97f0713a9be1c87535ee84ea6bdd7cea64e309d4c9c6f9d4c79371d4d5bf59d4d5c36a9ba1d0752ef0dca70d3914bdab429a61e2a64d3bd4bf834297b4ef5376f854d7b70299f55efe7f0f0d8365e7c3cec8e5e9", + "headers": [ + { + ":status": "200" + }, + { + "expires": "Sun, 04 Nov 2012 06:26:14 GMT" + }, + { + "last-modified": "Wed, 05 Sep 2012 21:17:19 GMT" + }, + { + "etag": "\"19309a1-2b15-e65bd5c0\"" + }, + { + "cache-control": "max-age=172800" + }, + { + "server": "Apache/2.2.3 (Red Hat)" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + }, + { + "p3p": "CP=\"NOI DSP COR LAW CUR ADMo DEVo TAIo PSAo PSDo IVAo IVDo HISo OTPo OUR SAMo BUS UNI COM NAV INT DEM CNT STA PRE LOC\", CP=\"NOI DSP COR LAW CUR ADMo DEVo TAIo PSAo PSDo IVAo IVDo HISo OTPo OUR SAMo BUS UNI COM NAV INT DEM CNT STA PRE LOC\"" + }, + { + "content-length": "3891" + }, + { + "content-type": "text/html; charset=UTF-8" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 98, + "wire": "48826402768586b19272ff7f01c7acf4189eac2cb07f33a535dc61848e65c72525a245c87a58f0c918ad9ad7f34d1fcfd297b5c1fcebdd09d4d7baf9d4d5c36a9ba1d0a6adfb54bbc37297f76b521cf9d4bdab6ff30f1fbe9d29aee30c2171d23f67a961c88f4849695c87a58292967fc3490472c827c3201613e369668ac8161b2312cf82394842b6191e97e0be601c9496891721e90f0d033237355f95497ca589d34d1f6a1271d882a60320eb3cf36fac1fcce90f28d3a4b449120a84411cb209f0c80584f8da59a2b20586c8c4b3e08e5210ad8647a5fb2f9acd615106eb6afa500da9a07e941002ca8066e36fdc642a62d1bfeeb1a67818fb90f48cd54091ccb8e4a4b448b90f4fdf", + "headers": [ + { + ":status": "302" + }, + { + "server": "Apache" + }, + { + "p3p": "policyref=\"http://tag.admeld.com/w3c/p3p.xml\", CP=\"PSAo PSDo OUR SAM OTR BUS DSP ALL COR\"" + }, + { + "location": "http://s.amazon-adsystem.com/ecm3?id=bfd291d0-29a4-4e30-a3a2-90bfcce51d8f&ex=admeld.com" + }, + { + "content-length": "275" + }, + { + "content-type": "text/html; charset=iso-8859-1" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + }, + { + "connection": "keep-alive" + }, + { + "set-cookie": "meld_sess=bfd291d0-29a4-4e30-a3a2-90bfcce51d8f;expires=Sun, 05 May 2013 03:59:31 GMT;path=/;domain=tag.admeld.com;" + } + ] + }, + { + "seqno": 99, + "wire": "886196dc34fd280654d27eea0801128166e01ab8c894c5a37f7685dc5b3b96cf58b1a47e561cc5801f4a547588324e5fa52a3ac849ec2fd295d86ee3497e94a6d4256b0bdc741a41a4bf4a216a47e47316007f4085aec1cd48ff86a8eb10649cbf6496df3dbf4a002a651d4a05f740a0017000b800298b46ff7f05c1acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7e94bdae0fe75ee84ea6bdd7cea6ae1b54dd0e85356fdaa5fddad4bdab6ff30f1fed9d29aee30c2171d23f67a961c88f4849695c87a5835acff92403a47ecf52e43d3f030c1f0314000802565f95d6c637d969c6ca57840138f3856656c51904eb4dc641ca2948db6524b204a32b8d3a171d1bcc9491c6dfc1e892239e007c5500596c2fb4ebce81e71bf89084813f4087aaa21ca4498f57842507417f0f28c11c8b5761bb8c9ea007da97cf48cd540b8e91fb3d4b0e447a424b4ae43d3f6a60f359ac2a20df3dbf4a002b651d4b080cbaa0017000b800a98b46ffb5358d33c0c77b9384842d695b05443c86aa6fae082d8b43316a4f5a839bd9ab0f0d0235375f87352398ac4c697f408725453d44498f57842507417f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:32 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\", CP=\"PSAo PSDo OUR SAM OTR DSP COR\"" + }, + { + "location": "http://s.amazon-adsystem.com/iu3?d=amazon.com&a1=&a2=0101e39f75aa93465ee8202686e3f52bc2745bcaf2fc55ecfd1eae647167a83ecbb5&old_oo=0&n=1351947870865&dcc=t" + }, + { + "nncoection": "close" + }, + { + "set-cookie": "ad-privacy=0; Domain=.amazon-adsystem.com; Expires=Thu, 01-Jan-2037 00:00:01 GMT; Path=/" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "57" + }, + { + "content-type": "image/gif" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 100, + "wire": "88c70f0d837de65a6c96df697e940b4a436cca080112807ae05fb8c814c5a37f5f911d75d0620d263d4c795ba0fb8d04b0d5a75892aed8e8313e94a47e561cc581c132e880265b6496d07abe9403ea436cca080c89408ae341b8d38a62d1bfdb408721eaa8a4498f5788ea52d6b0e83772ff7b8b84842d695b05443c86aa6fc6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "content-length": "9834" + }, + { + "last-modified": "Tue, 14 Aug 2012 08:19:30 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "cache-control": "public, max-age=623720235" + }, + { + "expires": "Mon, 09 Aug 2032 12:41:46 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 101, + "wire": "88cd6c96e4593e94136a612c6a08007d40b771a7ee34f298b46f5f86497ca582211fc0c8588da47e561cc581b780db4e3afbdff0e00f0d03333439c2408af2b10649cab5073f5b6b9bd19376e525b0f4a8492a58d48e62a171d23f67a9721e9b81001e07", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 25 Feb 2009 15:49:48 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "max-age=580546798" + }, + { + "expires": "Thu, 10 Jun 2032 16:40:29 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:31 GMT" + }, + { + "content-length": "349" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-lookup": "MISS from cdn-images.amazon.com:10080" + } + ] + }, + { + "seqno": 102, + "wire": "885f88352398ac74acb37f0f0d841381087fc46196df3dbf4a002a693f750400894102e36edc0054c5a37fd35891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96df3dbf4a002a693f750400894102e36edc0054c5a37f55850b4d34d87f7f24ad0b5f8ee18f678c991de4c8b3628aeb9c2ad9ee187481accd13de4ef5ecfca24b32f5f415b945873640784430417caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "26111" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 20:57:01 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 20:57:01 GMT" + }, + { + "age": "144451" + }, + { + "x-amz-cf-id": "14X7FbQwII7W32KG_B6UnQzAAN04K4czIvpQXldrJky1-W_FKI0wsA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 103, + "wire": "886496d07abe9413ea6a2254100225040b8066e084a62d1bff6c96df697e940814cb6d0a080112807ee36ddc13aa62d1bf0f1391fe42fb20189b584223ab46392328480fe75891a47e561cc581f034000000fa52bb63a0c4e5e4d6e30f0d8313a07bd2decfce", + "headers": [ + { + ":status": "200" + }, + { + "expires": "Mon, 29 Oct 2012 20:03:22 GMT" + }, + { + "last-modified": "Tue, 10 Jul 2012 09:55:27 GMT" + }, + { + "etag": "\"1930a25-22c7-badbe1c0\"" + }, + { + "cache-control": "max-age=90400000, public" + }, + { + "server": "Apache/2.2.3 (Red Hat)" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + }, + { + "p3p": "CP=\"NOI DSP COR LAW CUR ADMo DEVo TAIo PSAo PSDo IVAo IVDo HISo OTPo OUR SAMo BUS UNI COM NAV INT DEM CNT STA PRE LOC\", CP=\"NOI DSP COR LAW CUR ADMo DEVo TAIo PSAo PSDo IVAo IVDo HISo OTPo OUR SAMo BUS UNI COM NAV INT DEM CNT STA PRE LOC\"" + }, + { + "content-length": "2708" + }, + { + "content-type": "application/x-javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:04:32 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 104, + "wire": "88dd6c96e4593e940bca435d8a0801128105c69db81794c5a37fd3cfd75893aed8e8313e94a47e561cc581c132eb2fb4f3ff6496d07abe9403ea436cca080c8940bd7002b8d054c5a37fe10f0d83138217d2cd", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 18 Apr 2012 10:47:18 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=623739489" + }, + { + "expires": "Mon, 09 Aug 2032 18:02:41 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:32 GMT" + }, + { + "content-length": "2622" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-lookup": "MISS from cdn-images.amazon.com:10080" + } + ] + }, + { + "seqno": 105, + "wire": "886196dc34fd280654d27eea0801128166e01ab8cb2a62d1bfe56c96c361be940094d27eea0801128205c033702da98b46ff6496df697e94038a693f750400894102e019b816d4c5a37f0f139fc17e186fc20bb76f8b036d05e730dd75ebf82f5fbaf030070000fb408597e158a4a47e561cc5804f32e883f55db1d0627d54759360ea44a7b29faa6d4256b0bdc741a41a4b408df2b1c88ad6b0b59ea90b62c693884bc59083391158bf0f0d033437317f18842507417f5f911d75d0620d263d4c1c88ad6b0a8acf520b", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:33 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Fri, 02 Nov 2012 20:03:15 GMT" + }, + { + "expires": "Tue, 06 Nov 2012 20:03:15 GMT" + }, + { + "etag": "EDAADA0BBD2E54186FB78DECDB80E1E00940A39A" + }, + { + "cache-control": "max-age=283721,public,no-transform,must-revalidate" + }, + { + "x-ocsp-reponder-id": "t8edcaocsp2" + }, + { + "content-length": "471" + }, + { + "connection": "close" + }, + { + "content-type": "application/ocsp-response" + } + ] + }, + { + "seqno": 106, + "wire": "88c4eb6c96dc34fd280654d27eea0801128172e32ddc65953168df6496e4593e9403aa693f7504008940b97196ee32ca98b46f0f13a00bafde8458306115d770e07dcbb79f7ef362bed3b7430b981fc0cb9870b30bbf58a5a47e561cc58196dd71b7feabb63a0c4faa8eb26c1d4894f653f54da84ad617b8e83483497fc30f0d03343731c2c1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:33 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Sat, 03 Nov 2012 16:35:33 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 16:35:33 GMT" + }, + { + "etag": "179CA2EEF2B7FE96BC99C52D47B1A6E9E36FF3A7" + }, + { + "cache-control": "max-age=357659,public,no-transform,must-revalidate" + }, + { + "x-ocsp-reponder-id": "t8edcaocsp2" + }, + { + "content-length": "471" + }, + { + "connection": "close" + }, + { + "content-type": "application/ocsp-response" + } + ] + }, + { + "seqno": 107, + "wire": "88eb76b686b19272b025c4bb4a7f5c2a379fed4bf0f1604a5279224228604b897694d5596addbb3b005df5dd1a949e48a51a12498cc09769717f0f28dacd0dfe1bb06dbdab566c982085c78576f32fad81f65959a8705f59e72fb2b38fc30e01430df08b0fda921e919aa82bb63a469311721e9fb50be6b3585441badabe94032b693f758400b2a059b806ae32253168dff6a5634cf031dce47f28e2bdae0fe74eac8a5fddad4bdab6a99e1e4a5ee1b5486fe83a97f0713a9be1c87535ee84ea6bdd7cea64e309d4c9c6f9d4c79371d4d5bf59d4d5c36a9ba1d0752ef0dca70d3914bdab429a61e2a64d3bd4bf834297b4ef5376f854d7b70299f55efe7fc4798624f6d5d4b27f5f87497ca589d34d1f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:32 GMT" + }, + { + "server": "Apache/2.2.4 (Unix) DAV/2 mod_ssl/2.2.4 OpenSSL/0.9.7a mod_fastcgi/2.4.2" + }, + { + "set-cookie": "KADUSERCOOKIE=A682BC39-E933-4AED-86D3-69AAE2AAD12F; domain=pubmatic.com; expires=Sun, 03-Nov-2013 13:04:32 GMT; path=/" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "p3p": "CP=\"NOI DSP COR LAW CUR ADMo DEVo TAIo PSAo PSDo IVAo IVDo HISo OTPo OUR SAMo BUS UNI COM NAV INT DEM CNT STA PRE LOC\"" + }, + { + "connection": "close" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-type": "text/html" + } + ] + }, + { + "seqno": 108, + "wire": "88cbeeedecebea0f1fed9d29aee30c2171d23f67a961c88f4849695c87a5835acff92403a47ecf52e43d3f030c1f0314000802565f95d6c637d969c6ca57840138f3856656c51904eb4dc641ca2948db6524b204a32b8d3a171d1bcc9491c6dfc1e892239e007c5500596c2fb4ebce81e71bf89084813fe90f28c11c8b5761bb8c9ea007da97cf48cd540b8e91fb3d4b0e447a424b4ae43d3f6a60f359ac2a20df3dbf4a002b651d4b080cbaa0017000b800a98b46ffb5358d33c0c7e8e70f0d023537e6e5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:33 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\", CP=\"PSAo PSDo OUR SAM OTR DSP COR\"" + }, + { + "location": "http://s.amazon-adsystem.com/iu3?d=amazon.com&a1=&a2=0101e39f75aa93465ee8202686e3f52bc2745bcaf2fc55ecfd1eae647167a83ecbb5&old_oo=0&n=1351947870865&dcc=t" + }, + { + "nncoection": "close" + }, + { + "set-cookie": "ad-privacy=0; Domain=.amazon-adsystem.com; Expires=Thu, 01-Jan-2037 00:00:01 GMT; Path=/" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "57" + }, + { + "content-type": "image/gif" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 109, + "wire": "88cb76bd86b19272b025c4bb4a7f5c2a379fed4bf0f1604a5279224228604b897694d5596addbb3b005df5de2ad29ab42d64e5a1b5293c914a34249319812ed2e2e0e8c1c7c0bf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:33 GMT" + }, + { + "server": "Apache/2.2.4 (Unix) DAV/2 mod_ssl/2.2.4 OpenSSL/0.9.8e-fips-rhel5 mod_fastcgi/2.4.2" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "p3p": "CP=\"NOI DSP COR LAW CUR ADMo DEVo TAIo PSAo PSDo IVAo IVDo HISo OTPo OUR SAMo BUS UNI COM NAV INT DEM CNT STA PRE LOC\"" + }, + { + "connection": "close" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-type": "text/html" + } + ] + }, + { + "seqno": 110, + "wire": "88ef6c96c361be941094d444a820040a05fb8c86e36053168dffdfe1e9588da47e561cc581b7996df6df71efcf6196dc34fd280654d27eea0801128166e01ab8cb4a62d1bf0f0d03333535e4df", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 22 Oct 2010 19:31:50 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "max-age=583595968" + }, + { + "expires": "Mon, 09 Aug 2032 18:02:41 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:34 GMT" + }, + { + "content-length": "355" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-lookup": "MISS from cdn-images.amazon.com:10080" + } + ] + }, + { + "seqno": 111, + "wire": "88f26c96df697e940b8a6a2254100225041b8072e05b53168dffe8e4ec5893aed8e8313e94a47e561cc581c640d3cd85e77f6496df697e94138a6a2254101912817ee361b800a98b46ffc10f0d836597dee7e2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 16 Oct 2012 21:06:15 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=630485187" + }, + { + "expires": "Tue, 26 Oct 2032 19:51:01 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:34 GMT" + }, + { + "content-length": "3398" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-lookup": "MISS from cdn-images.amazon.com:10080" + } + ] + }, + { + "seqno": 112, + "wire": "88e10f0d84134cb4dfe76196e4593e940814d444a820044a00171976e05f53168dfff6e0df6c96df697e9403ea6a225410022504cdc133700d298b46ff55851042f34cb77f1faf4e7d4df5b39477b9d3b697f3e4e9f97f4bae4b7857f2cfbf71b46767979775b78b7c5b25a396cf7d3a7acfbbc4107fdedd", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "24345" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 10 Oct 2012 00:37:19 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 09 Oct 2012 23:23:04 GMT" + }, + { + "age": "2118435" + }, + { + "x-amz-cf-id": "tLO5krWbCYmRm9LIjXDN76fC2DJhTSiML3Wx7P5GT_QflWQzjjyLSw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 113, + "wire": "887685dc5b3b96cf6c96d07abe9403ea435d8a080112806ae01eb8dbca62d1bff0ecf45893aed8e8313e94a47e561cc581c0b2cba20bacff6496dd6d5f4a042a435d8a080c8940bd702d5c03aa62d1bfc90f0d8365f745efea", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Mon, 09 Apr 2012 04:08:58 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=613372173" + }, + { + "expires": "Sun, 11 Apr 2032 18:14:07 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:34 GMT" + }, + { + "content-length": "3972" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-lookup": "MISS from cdn-images.amazon.com:10080" + } + ] + }, + { + "seqno": 114, + "wire": "88e90f0d84105d659fef6196dd6d5f4a082a6a2254100225001b8cb37196d4c5a37fc2e8e76c96d07abe9403ca6a2254100225040b8066e040a62d1bff5585085c69c7017f06ad8cfe817fa73c86eefec35fe10dd6f26f0327cbe01a8ab6ff7b3b0bd8f3c71de796d17b74543996cf72b734107fe6e5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "21733" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 21 Oct 2012 01:33:35 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 08 Oct 2012 20:03:10 GMT" + }, + { + "age": "1164660" + }, + { + "x-amz-cf-id": "boy0DjYIiv9QiDUAB5IT03oJw0Oe-TzQq2zaLbbC8-MCS_l6Jrzf5g==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 115, + "wire": "88c56c96c361be940094d27eea080112806ee36ddc69b53168df5f911d75d0620d263d4c795ba0fb8d04b0d5a7f45a839bd9ab5893aed8e8313e94a47e561cc581c640e01e132eff6496df3dbf4a09e535112a080c8940397001b8d894c5a37f6196dc34fd280654d27eea0801128166e01ab8cb6a62d1bf0f0d840bae360f7f2088ea52d6b0e83772fff5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 02 Nov 2012 05:55:45 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=630608237" + }, + { + "expires": "Thu, 28 Oct 2032 06:01:52 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:35 GMT" + }, + { + "content-length": "17650" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-lookup": "MISS from cdn-images.amazon.com:10080" + } + ] + }, + { + "seqno": 116, + "wire": "88f40f0d84132fbee7be6196e4593e94134a6a2254100225000b8cbf71b654c5a37fcdf3f26c96d07abe9403ca6a225410022502e5c0bd71a7d4c5a37f55857c0f38f0bf7f09aeed7a2dbc73839e7f3ccf77b8ff416f68d02e391ce9826e1d596b6f50bfdabc526e171461e1b99cdd7973d134107ff1f0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "23996" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 24 Oct 2012 00:39:53 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 08 Oct 2012 16:18:49 GMT" + }, + { + "age": "908682" + }, + { + "x-amz-cf-id": "qC_RVL0YLxYoBvaZ0uqbs2VI6jEgUk34Rk19qpGdS2VsFUS3KkWYMg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 117, + "wire": "885f88352398ac74acb37f0f0d84105b785fc36196c361be940bea6a225410022502f5c69db8dbca62d1bfd26c96d07abe9403ca6a2254100225040b8066e042a62d1bff5891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f5585089d6d97dd7f05aeef8fb66dc9dbf9975f341b37bdc04eff1ce20fe663754398fbf8cb8b7bfc21b37512c28735557b9e3d8ed10c107f7caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "21582" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 19 Oct 2012 18:47:58 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Mon, 08 Oct 2012 20:03:11 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "1275397" + }, + { + "x-amz-cf-id": "vHqKStRXJPYsiKzS0tTwY_1XKiks6HvwJGT9UArSlfAs6OnCYHQ7lA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 118, + "wire": "88c60f0d841082cbffcb6196df3dbf4a05e535112a080112807ee01bb81714c5a37fdac4c36c96d07abe9403ca6a2254100225040b8066e044a62d1bff55860b2fb8eb6fff7f04ac64ddb759e806678cfdbd5b7781d636de4cf09b06edda6eec668eed732e14f9d66896849dda73a25cc90f8820c3c2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "22139" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 18 Oct 2012 09:05:16 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 08 Oct 2012 20:03:12 GMT" + }, + { + "age": "1396759" + }, + { + "x-amz-cf-id": "3iqSry0i3VhqyuBUo-iRW3UgESSNBQ3lv4YeFtxPi_-Acv46jt6IAw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 119, + "wire": "885f87352398ac5754df0f0d03383439d06196df3dbf4a09d53716b5040089403b704f5c0bca62d1bfdfc9c86c96dc34fd281714cb6d0a08010a8005c641704253168dff5585644171f75d7f03aee93b2e6ed1bbf07f9d22de5dbb5ff6d7143cb1238f01f1edc597afb5bdf2f1daadf7fad4cd3c1de5cf264cfe2083c8c7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "849" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 27 Sep 2012 07:28:18 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Sat, 16 Jul 2011 00:30:22 GMT" + }, + { + "age": "3216977" + }, + { + "x-amz-cf-id": "jh36SMSXaXj_TeRR9z4Vs8-cbbEoHRGJkz-zWwqnTDkn3mU7WYIILw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 120, + "wire": "88e26c96df697e940baa651d4a080112800dc0bd719794c5a37fda7b8b84842d695b05443c86aa6fda5893aed8e8313e94a47e561cc581c0b2cb6e884d7f6496dd6d5f4a042a435d8a080c8940b5700d5c6df53168dfd90f0d8313206fd8408af2b10649cab5073f5b6b9bd19376e525b0f4a8492a58d48e62a171d23f67a9721e9b81001e07", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 17 Jan 2012 01:18:38 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=613357224" + }, + { + "expires": "Sun, 11 Apr 2032 14:04:59 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:35 GMT" + }, + { + "content-length": "2305" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-lookup": "MISS from cdn-images.amazon.com:10080" + } + ] + }, + { + "seqno": 121, + "wire": "885f87352398ac4c697f0f0d83138d37da6196df697e940b8a6a225410022502d5c6c1704f298b46ffe9d3d26c96c361be941094d444a820040a099b8db3704ea98b46ff55860b6d3cf34f7f7f08acdbcfc27cb6303e54d83fdc746a7b8256de41fb46c9c7934518d17a2cbbfddec1f90f32fe4b4c73821e50c107d2d1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "2645" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 16 Oct 2012 14:50:28 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 22 Oct 2010 23:53:27 GMT" + }, + { + "age": "1548848" + }, + { + "x-amz-cf-id": "RYwtx5a09etraZHlO8Ut-TcazsQhaIMlHsC_JTzCEXAYeXfmbh0AWA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 122, + "wire": "88ec6c96df697e941094d03f4a0801128105c037702253168dffe4c7e35893aed8e8313e94a47e561cc581c132eb2fb4f3ff6496d07abe9403ea436cca080c8940bd7002b8d34a62d1bfe20f0d03363536e1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 22 May 2012 10:05:12 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=623739489" + }, + { + "expires": "Mon, 09 Aug 2032 18:02:44 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:35 GMT" + }, + { + "content-length": "656" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 123, + "wire": "88c50f0d03353930e16196df3dbf4a05f532db42820044a01db8072e05f53168dff0dad96c96e4593e94038a6a2254100205040b8d3b702f298b46ff55857c4e3827dd7f05ade9a3ac5cb3c2ea76e21fbeff8c98a5abf5eb75e7ebaae874f1e95514dbb716bdbae5e18ab9253d797968b66820d9d8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "590" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 19 Jul 2012 07:06:19 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 06 Oct 2010 20:47:18 GMT" + }, + { + "age": "9266297" + }, + { + "x-amz-cf-id": "jMk_WLA7tRGazvX3ieenZ8uPLkOB1NVjnlmuRGPRPfUGpdfopJWMug==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 124, + "wire": "88d30f0d03363138e56196c361be940bea6a225410022502edc0b971b7d4c5a37ff46c96c361be940b4a6e2d6a080112816ee05bb8c854c5a37fdfde5585089e03cdbb7f02ac9914ad6728ba47876945a4af8b5492481676e035daaffb3bfd5b675b6fe7e4b39cbf67e987318535649a083fdddc0f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "618" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 19 Oct 2012 17:16:59 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 14 Sep 2012 15:15:31 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "1280857" + }, + { + "x-amz-cf-id": "gsm-rW_jbFRe2Ne92Oddd13REiBnDzo9k53P59LW-6WZhjFKi2gpcg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 125, + "wire": "88d70f0d8371a03fe96196dc34fd280714d444a820044a01bb820dc6de53168dfff8e2e16c96e4593e94136a435d8a080112806ee01db8cbea62d1bf5586134d38fb6f7f7f02adda5aaf84e3d17eebbcafc20ab2dd36f75ed777a55d3853cf82da77b2b177519ff00cf66fbe80b94ec4cf34107fe1e0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "6409" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 06 Oct 2012 05:21:58 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 25 Apr 2012 05:07:39 GMT" + }, + { + "age": "2446958" + }, + { + "x-amz-cf-id": "RenD1oaMDB7WDA0nJBiT78PBjnjUmYU-NT3-eSlLX03q5vM16mQthg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 126, + "wire": "88d10f0d840b2f85bfed6196df697e94640a6a225410022500ddc6ddb8d054c5a37f7685dc5b3b96cfe7e66c96d07abe9413ea6a225410022502fdc106e05a53168dff558565d0882dff7f03adf4b76ebea2b77702c75fb3f3cd8b9e81868f788bc2df2d4b3554ed9fc759bbd9f9b2a75ea31370cf2e1a61820fe6e5408725453d44498f57842507417f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "13915" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 30 Oct 2012 05:57:41 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 29 Oct 2012 19:21:14 GMT" + }, + { + "age": "371215" + }, + { + "x-amz-cf-id": "y-qky_uSUebpzoYKGYMa1lzGeUux4fgnmRhwkgvrXQn78lG5AhfFmA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 127, + "wire": "88c26c96df697e941094d03f4a0801128072e341b8cbaa62d1bf5f86497ca582211fddf95893aed8e8313e94a47e561cc581c10596d9742eff6496df697e940b2a65b685040644a019b817ee36ca98b46f6196dc34fd280654d27eea0801128166e01ab8cb8a62d1bf0f0d830b6f03f8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 22 May 2012 06:41:37 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=621353717" + }, + { + "expires": "Tue, 13 Jul 2032 03:19:53 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:36 GMT" + }, + { + "content-length": "1580" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 128, + "wire": "88f30f0d8369a6dcf86196e4593e94642a6a225410022500ddc13971b654c5a37fc8f1f06c96e4593e94642a6a225410022500ddc1397191298b46ff558513ce38e33f7f08add90f2c30e13ecedda4cce4682c76f000704b8b8def1bb8c58fa2778f3c2b4f46f861d50b9235c91edaf134107ff0ef", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4456" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 05:26:53 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 05:26:32 GMT" + }, + { + "age": "286663" + }, + { + "x-amz-cf-id": "QAWFAFoQqqdK6bsebuU01EfGVCwSV_HjtTaLA-hlTAAOA6d4Wsz4wg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 129, + "wire": "88f70f0d8313ed87408721eaa8a4498f5788ea52d6b0e83772ff6196c361be940094d27eea080112817ae32f5c682a62d1bfcd6c96e4593e94642a6a225410022500ddc139719694c5a37ff7f6558471c65b6f7f03ad86d8e36f45b77509ebe2c8f74c97f8f3e54e412cf4cd6f48095d0e465d04af8eed3bff1b9bf74776bc4db2083ff5f40f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2951" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 02 Nov 2012 18:38:41 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 31 Oct 2012 05:26:34 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "66355" + }, + { + "x-amz-cf-id": "Aubb5MuBO28D2I8jIDVYWmI2-8g4Tt0cpl6beMcpVSNTX5gZMv4wgQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 130, + "wire": "885f88352398ac74acb37f0f0d8369c71cc36196df3dbf4a002a693f7504008940bb71b7ae32d298b46fd25891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96df3dbf4a002a693f7504008940bb71b7ae32d298b46f55850b6d85c17f7f05add57654b3eabaedddbfd646ced8bf770dba258fa7ef2ad9dd3fbc2f2d26869bd7283b75cb939ce26aad89d9041f7caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4666" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 17:58:34 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 17:58:34 GMT" + }, + { + "age": "155162" + }, + { + "x-amz-cf-id": "OBft3yppuSTyI5o52ZSa5lfbjZWp3ShzF8-dM45Pf0qkJIYh24nQtQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 131, + "wire": "88c60f0d836c2f8bcb6196df3dbf4a002a693f7504008940b97197ee34fa98b46fdac5c46c96e4593e94136a65b6850400894086e045719794c5a37f558471f7c4cf7f04afe6e6e6bd556f693972bfa64c5a7bfe694f0725ab4ddbba8f973f2e3ae49e6abd60ab7efd17fbe489afcb747bd9041fc3c2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5192" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 16:39:49 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 25 Jul 2012 11:12:38 GMT" + }, + { + "age": "69923" + }, + { + "x-amz-cf-id": "Y6S4ynuqdWWDNdGNvXNtU6fnNBBOoJLWVPdhgnyEnTTMDvI_4XuMzQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 132, + "wire": "88ca0f0d836db13fcf6196e4593e94642a6a225410022500cdc683702253168dffdec9c86c96e4593e94642a6a225410022500cdc0b7704e298b46ff558513ec800d7f7f02addd3e8f07fcf54bdf9a71b5b832e1fc0b7bee93a5d31f98e6bd94776bb2fc7d87a9397a6a369ebca42ccecd041fc7c6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5529" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 03:41:12 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 03:15:26 GMT" + }, + { + "age": "293004" + }, + { + "x-amz-cf-id": "ShMwoXym8XNH4S1fFX15TBcjBioYagCJaBprDbqaOtJjOiNkWdeg7g==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 133, + "wire": "88ce0f0d8365c13fd36196c361be940bea6a225410022504cdc0b771a7d4c5a37fe2cdcc6c96dc34fd282654cb6d4a080112820dc65db81654c5a37f5585089b7d913b7f02adc747ad1b9a69c40de79cb753b7fa65f5c07df90d6a71e307e6727af7c2c8da7f32d89aa7af6d11eed48c90c107cbca", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3629" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 19 Oct 2012 23:15:49 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Sat, 23 Jun 2012 21:37:13 GMT" + }, + { + "age": "1259327" + }, + { + "x-amz-cf-id": "HlyMS446sa886uO7DjJyUavWa-mHH0XLcyzUrb49K-G4mkqMbSOsIA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 134, + "wire": "88d20f0d8365b03fd76196c361be94136a681fa50400894133700cdc036a62d1bfe6d1d06c96df3dbf4a09a5340fd2820044a05eb8cb7702f298b46f55860b2fb8079f0f7f02ac5b27ead9bdc94347dfa1d75cf312dba4c8b6e956b24bb801e4b9b9f7a7a08e9d5a6eb535b6b34028d2886083cfce", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3509" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 25 May 2012 23:03:05 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 24 May 2012 18:35:18 GMT" + }, + { + "age": "13960891" + }, + { + "x-amz-cf-id": "-IZ-Kzdl4oTM776x_-SdI-Sf-rdBE0xeKYvmj2otONB4guu3l0lNsA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 135, + "wire": "88d60f0d8369e75fdb6196e4593e94642a6a225410022500d5c65fb81694c5a37fead5d46c96e4593e94642a6a225410022500cdc0b571b6d4c5a37f558513cfb6217f7f02aee1575f78337e48d5ff18bbe7e19d67961cbcbdf1c7a0975adeb279d8fad8f3f2c3b512c66c9df9faa3c64f30c107d3d2e9", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4879" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 04:39:14 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 03:14:55 GMT" + }, + { + "age": "289522" + }, + { + "x-amz-cf-id": "UnkzEKXd4DwGvLUL-8-afWzVHMcB4T-tYr9-HLWFRsfbiIvYylwIxA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 136, + "wire": "88da0f0d8313c07fdf6196c361be940094d27eea0801128176e09ab8d34a62d1bfeed9d80f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96df697e940854dc5ad4100225021b8d06e09b53168dff5584740ebe2f7f02ad86bd9b89ef6bbde66f65e93ce3fddae4f7690d71d0b4f8bbd7e757b7346badf2dec07bbe5f7f23cb0d3f934107d7d6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2809" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 02 Nov 2012 17:24:44 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Tue, 11 Sep 2012 11:41:25 GMT" + }, + { + "age": "70792" + }, + { + "x-amz-cf-id": "ApQSczR7vg5QCdxHZR6hBm1pbl-hGvpxOz6MPp9eCEoBx99I8-atXg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 137, + "wire": "88de0f0d830beebfe36196e4593e94642a6a225410022502f5c0b3702f298b46fff26c96e4593e94642a6a225410022502f5c0b3702f298b46ffdedd5585134071d7bf7f02aee5dfbeee5bb1478aeedc765d941b70f4cacf6cb11daf4ffacb6ef7b71715fec192be7dfc6f874d9e3d58b6af1041dbda", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1979" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 18:13:18 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 31 Oct 2012 18:13:18 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "240678" + }, + { + "x-amz-cf-id": "WvvSWSGbGBRHrBf0RFjJ3qJ_o4y9yJuT8SeGDq1dpYvwTANrwyr-Ow==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 138, + "wire": "88e20f0d8371f685e7e1f5dee0dfdd7eacfc862e8cd3c7c74465d1be6c6523383468df95b69df60fca726bc6f4f4684b69b740756028b35a78a17a6820dcdb0f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6942" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 17:58:34 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 01 Nov 2012 17:58:34 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "155162" + }, + { + "x-amz-cf-id": "XA_j3mVwjsJMTgHec3EMMTJ547z0XmIPH8hlMt5tuM1OEe2Kuo_A8g==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 139, + "wire": "88e30f0d830bcdb5e8d6f6e1e06c96e4593e94642a6a225410022500cdc0b7704da98b46ffd57f00adb35e9e19620736adc00e59b3eac3fb249c52feb4ac16cde48f687f973c3e7f49e7876101ad7825d29e36f8820fdedd", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1854" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 03:41:12 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 03:15:25 GMT" + }, + { + "age": "293004" + }, + { + "x-amz-cf-id": "rPNUJ_0Y4uE0WKLOFZddVt9Pt-15ixc8M9WYFxZcxUq204PEfNtVuw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 140, + "wire": "88e50f0d8365c703ea6196df3dbf4a002a693f7504008940b97040b8d814c5a37f7685dc5b3b96cfe5e46c96df697e940854dc5ad4100225041b8d06e084a62d1bff55840b81009c7f03ae083973efbebcba7063068875b3e2a282bfcfcd381efdaa1c8d178e5859c17f5d995cdf859b3b839e5cb9a286083fe3e2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3661" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 16:20:50 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 11 Sep 2012 21:41:22 GMT" + }, + { + "age": "161026" + }, + { + "x-amz-cf-id": "10WYvTpJNEH0MAP3wne0pXXNE8ZnAI4eVJA3EDPrJ6TF3rv0YJJK_A==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 141, + "wire": "88ea0f0d8313a27fef6196df3dbf4a002a693f75040089413371a66e34ca98b46fc2e9e86c96e4593e940054cb6d4a08010a806ee09ab8cb6a62d1bf5584740eb6ff7f02adeb18aad1dbba44fece8ce7909e3739e3c0731e3d19fdf0a32f2e9ef07b972b4fcf664e7c5f8493ea1d556c820fe7e6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2729" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 23:43:43 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 01 Jun 2011 05:24:35 GMT" + }, + { + "age": "70759" + }, + { + "x-amz-cf-id": "kb2nMqvt29Qj3LdcwS6ww1KobMLzUlJWjzEzfJ49hrIYV9AchOannQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 142, + "wire": "88ee0f0d8365f0b9f3d1c5eceb6c96c361be9403aa6e2d6a080112807ee019b8cbaa62d1bfd07f00aecb97f6fc7a36b5b73e9cd163e3df7c7c2ad23be6a2f09aaf25da53cddbbb646f2f5d9ea3c7367d7973d82bbe2083e9e8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3916" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 02 Nov 2012 17:24:44 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 07 Sep 2012 09:03:37 GMT" + }, + { + "age": "70792" + }, + { + "x-amz-cf-id": "JJZDbMR4RLNK_HVvTbUnNaDilC24pIBmtY7BRd5JkQybHgLPJLr2Bw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 143, + "wire": "88f00f0d831040cff56196e4593e94642a6a225410022502f5c0b5702da98b46ffc8efee6c96e4593e94642a6a225410022502f5c0b5702da98b46ff5584134070417f02ac8713f38227e1ddb6d9c79fa4f75a9511f7c0e754d651c791beefee5a5dd962cc1ddaa4b1e475f28d8f43041fedec", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2103" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 18:14:15 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 18:14:15 GMT" + }, + { + "age": "240621" + }, + { + "x-amz-cf-id": "AG9h0_9ASRuhaLjhB4fsbvE6ktpeabI5v9S-fSJ_K1SOdr8skxsQ8A==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 144, + "wire": "88f40f0d8310440f408721eaa8a4498f5788ea52d6b0e83772ff6196e4593e94642a6a225410022502f5c13f71b1298b46ffcdf4f36c96e4593e94642a6a225410022502f5c0b7700e298b46ff5585132fb8f35f7f03ad678bfef7092e874e6d58fa116f71ec3171f87af85bbb85e7b1e86eff1d9cdfe8dfb77b0fce07b2dfc71fa86083f2f1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2120" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 18:29:52 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 18:15:06 GMT" + }, + { + "age": "239684" + }, + { + "x-amz-cf-id": "3V9zS2t71NKOHjc-zbQieHw8D15BF88HM5DVQY9j5z7qaxE8JDHbyA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 145, + "wire": "885f88352398ac74acb37f0f0d83101c6fc36196c361be940094d27eea0801128176e09bb82794c5a37fd25891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96df697e9403ea6a225410022502edc69eb8cb2a62d1bf5584740eb4f77f05add3d66a25ef113c8ab92c160fdcb8417da75b3616f3b79f33987c93b024e17f5eef37b8e39537b9a66c754d041f7caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2065" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 02 Nov 2012 17:25:28 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 09 Oct 2012 17:48:33 GMT" + }, + { + "age": "70748" + }, + { + "x-amz-cf-id": "Nkglfv_cx2pdr2EZJF0D475iF5L5LK6Fxcq0dUDPSxCVHftCYtgHng==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 146, + "wire": "88c60f0d8310590fcbcfd9c4c3cecd7f01adfcaddd61fdbd84193a66c49bbb6cb726845e75e0134d9e67d395b7dc017a5d0b36fe4aca2d67463d0eebd9041fc0bf408725453d44498f57842507417f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2131" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 18:14:15 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 18:14:15 GMT" + }, + { + "age": "240621" + }, + { + "x-amz-cf-id": "Xp7P1ZCF0IjKGtBRruIMsC780cNrxhNJ5960ejB13uXf3su3MHM7PQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 147, + "wire": "88c80f0d8313ee0bcd6196df3dbf4a002a693f75040089403b7000b800298b46ffdc6c96e4593e94642a6a225410022500ddc69fb8db2a62d1bfc8c755850bed38eb9f7f03adacbbfde1cb63617229755dd9cdbd579f0e1e58074f2bfa61ed9277a2a03c78187dacd1b66c56d67b5d6fc4107fc5c4", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2962" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 07:00:00 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 31 Oct 2012 05:49:53 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "194676" + }, + { + "x-amz-cf-id": "peTzFJr516_fOBQY5OC91FWEamWDNAqIh8_l1VUiaqrMRgGupou75w==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 148, + "wire": "88cc0f0d8369c139d16196df3dbf4a002a693f75040089403b7000b801298b46ffe0cbca6c96e4593e94642a6a225410022500ddc69fb8d894c5a37f55850bed38eb5f7f02ade3a0ad74fd8dda4dfa0c4b59dd9b6eebd34c9bebe7c32739baf47b80ff37e3a30febc8905bb569f66ce49e6820c9c8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4626" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 07:00:02 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 05:49:52 GMT" + }, + { + "age": "194674" + }, + { + "x-amz-cf-id": "VMe4jZb7miZ0G-rv3uBPNmdTpYUIYgkj8UaXTHlFZ8sd2SONziLchg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 149, + "wire": "88d00f0d8365b783d56196e4593e94642a6a225410022502edc13f7197d4c5a37fe46c96e4593e94642a6a225410022502edc13f7197d4c5a37fd0cf5585134c89f77f7f02adbfb3a8fbec9bde1d99f3698f12dcf8681c3939072ea1c868f578ef0c5e649c689f564cd9e5af65b0d429a1820fcdcc0f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3581" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 17:29:39 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 31 Oct 2012 17:29:39 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "243297" + }, + { + "x-amz-cf-id": "DQkavQgzFQLKNbG-YUMaAIW1JOadibOwvA_xdhashOIKLfpQuAn2gA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 150, + "wire": "88d40f0d836db65ad96196e4593e94642a6a225410022500ddc69fb8dbaa62d1bfe8d3d2c9558513cd89d7ff7f01add969d236726e7b4c44bfab1acdc336995cc87359972dee9cebc08df2fe65cde56d9cf53d69f5eb44d85bd9041fd0cf", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5534" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 05:49:57 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 05:49:53 GMT" + }, + { + "age": "285279" + }, + { + "x-amz-cf-id": "Quota3IS8N_cDOH-5AgNf6IoirJJCjYpEsTfXJKx-QYO8uoPPsgF5Q==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 151, + "wire": "88d70f0d8365965cdc6196e4593e94642a6a225410022500edc033702253168dffebd6d56c96e4593e94642a6a225410022500edc033702253168dff558513c079e6bf7f02adab361c9413659821bdea2f1a6ff8efa3aba76bc5fede0dda610a6df1c7e438a6da9cd9252ecbdcdab21e134107d4d3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3336" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 07:03:12 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 07:03:12 GMT" + }, + { + "age": "280884" + }, + { + "x-amz-cf-id": "nKFIlcQrEACy_wNDwvMk7o4wDqwiqg22gTbbx1GgRtKIfeQCY4rAUg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 152, + "wire": "88db0f0d8365c701e06196e4593e94642a6a225410022500edc037719794c5a37fefdad96c96e4593e94642a6a225410022500edc0357196d4c5a37f558513c07597bf7f02ad6e5a39fe76374e0d368a79e5b23ac6e5ff58766675fbb9b55e75bc98e286fc535dce38ef4a28a5732e56bc4107d8d7d5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3660" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 07:05:38 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 07:04:35 GMT" + }, + { + "age": "280738" + }, + { + "x-amz-cf-id": "5flYXqijU45smYJrbpa6DyFQK79BKOC75IH_AD_gBLabCf2_f6JJ4w==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 153, + "wire": "88df0f0d8365c643e46196e4593e94642a6a225410022500edc033704fa98b46fff36c96e4593e94642a6a225410022500edc033704fa98b46ffdfde558513c079c77f7f02ad7fda5de7d63f403b7565e127e539f86df1c713eeea271d2ef0bf4d1e7b76bb0d8e2dd6b2143a787eb3df1ec820dcdb", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3631" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 07:03:29 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 31 Oct 2012 07:03:29 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "280867" + }, + { + "x-amz-cf-id": "9zt7Ykby0o5nJUdXmLURwVG97OcVN7UDmlxqqBAr6-kpce1NUZ3vHQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 154, + "wire": "88e30f0d8369e643e86196e4593e94642a6a225410022500edc00ae34d298b46fff7e2e16c96e4593e94642a6a225410022500edc00ae34d298b46ff558413c07c227f02ac1d9554785a3a38cffaff2e5e4a1b3c07ae365ccfc7f6bb4bfb3ccde4e8ed6073dfaab5ebb75c5ae39e19a083e0df", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4831" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 31 Oct 2012 07:02:44 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 07:02:44 GMT" + }, + { + "age": "280912" + }, + { + "x-amz-cf-id": "arnnoA4osVhZ9WWxe1rw1kH36LVZpueZhg5Ij7p06zynPPuP_PbhAg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 155, + "wire": "885f87352398ac4c697f0f0d023731ed6196dd6d5f4a082a6a225410022500edc082e36da98b46ff7685dc5b3b96cf6c96df3dbf4a019532db52820040a00371b66e01d53168dfe9e85585085a69a1077f04ad3a78b73bfac037d63fbe9eb3b63469f46bfab477c0d12acb87be0e17b987b5ec9d6f6a0dda39e54615a5d9041fe6e50f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "71" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 21 Oct 2012 07:10:55 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 01:53:07 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "1144421" + }, + { + "x-amz-cf-id": "otV5h9P0a9-ozjyL5asNyiDOMvE4cnJFvEUCY1qCIkCO1BlYJsF-fQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 156, + "wire": "885f87352398ac5754df0f0d8369b13df36196c361be94138a6a225410022504cdc641702053168dffc3edec6c96dc34fd281654d444a820044a001700cdc69d53168dff558571b65c71cf7f03aef6348bddba7515bd7fbaf466fc7f2b79326020b077c58f90bc64e48c556bd789cd3ca4fc749c93d735dcecec820febea", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "4528" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 26 Oct 2012 23:30:10 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Sat, 13 Oct 2012 00:03:47 GMT" + }, + { + "age": "653666" + }, + { + "x-amz-cf-id": "zat2zuNOe5PZPMKX9J5IIEc2EvGHW2wIWsGnPPG6NWdX7cWtkKBL3Q==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 157, + "wire": "88c80f0d83699037f76196c361be940094d27eea080112817ee32d5c13aa62d1bfc7f1f06c96c361be940094d27eea080112817ae01db8d34a62d1bf5584719001ff7f02add7103123f689d6b67fcdba66a7471c9e6fedb113bd7b95ebb8f07e011a67c8ff2279cc8dae71af33dda50c3041efeeec", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "4305" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 02 Nov 2012 19:34:27 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 18:07:44 GMT" + }, + { + "age": "63009" + }, + { + "x-amz-cf-id": "P_0GsZlh-uhXRNgmMVIxDRrsh8CWCBHEX0sNhI9WcxKsR6VpK8qf1A==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 158, + "wire": "88cc0f0d8379903b408721eaa8a4498f5788ea52d6b0e83772ff6196df3dbf4a002a693f75040089403b7000b80754c5a37fcc6c96e4593e94642a6a225410022502fdc00ae09c53168dfff7f655850bed38e3ff7f03ac88389fd6dc59eabbe3ac5d5b85d7363444343d581623fcc5c1ccdaf5731f5e27e5923a07846916cb6df1041ff4f3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "8307" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 07:00:07 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 31 Oct 2012 19:02:26 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "194669" + }, + { + "x-amz-cf-id": "_1G9P5_LnBwk_k5A76Q4cs4aOE-c9Y2U6KPOYakVoWIblaFat2Quuw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 159, + "wire": "88cb0f0d8365e0bbc26196d07abe941094d444a820044a083704cdc0b4a62d1bffd05891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96df3dbf4a05e535112a0801128215c137719694c5a37f55850800e3cf0b7f04ad8d6b39dfd708e5d25d778bcd9b307e6ee16c5bb1db1779ff4fc655b92cf836e3e71ec33ba745985dc96f1f10417caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "3817" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 22 Oct 2012 21:23:14 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 18 Oct 2012 22:25:34 GMT" + }, + { + "age": "1006882" + }, + { + "x-amz-cf-id": "b-rYDPAafNePCeY3rEXSUu_SHu_vhZoVf-W-90RHYbQi7NMrF7IuVw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 160, + "wire": "885f88352398ac74acb37f0f0d840b42705fcb6196c361be94138a6a225410022502fdc6dab817d4c5a37fd9c6c56c96c361be94138a6a225410022502fdc699b8d894c5a37f558571c71c0bbf7f05aebfceecce6cbda7a42ff1711bb1593f86bc35b37acb4dec9923ebd3a65fdcdd2b9cd5fad4b139f8db9835bde2083fc4c3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "14262" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 26 Oct 2012 19:54:19 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 26 Oct 2012 19:43:52 GMT" + }, + { + "age": "666617" + }, + { + "x-amz-cf-id": "DYBg6QCNjA9V6sSGrhw4w4QT--gzcIbkjjJZKjphipyO-cYwRK1p8w==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 161, + "wire": "886196dc34fd280654d27eea0801128166e01ab8cb8a62d1bfdd4088f2b0e9f6b1a4583f9105f7777eb3a6ffe7e6c08330398758b97f4003703370ff12acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7eaf6b83f9bd0ea52feed6a67879297b86d521bfa14c9c613a9938df3a97b5693a9ab7eb3a9ab86d52fe0ce6535f0ba65356fda652ef0dca6bc7cd4d5a73a9c34e4535f0daa61c9a54bdab429a61e2a64d3bd4bf834297b4ef5376f854c7821535edc0a67d5794c5ab8a9ab7de53f94088f2b0e9f6b1a4585fb6e73f8ff75fad3279fce32eaf7fecb576c1afdb666bcdb1f3af5fbab87563f5de86ba4ecc59578ccde6f1dc124ade197fdc62dba899ff7b9384842d695b05443c86aa6fae082d8b43316a4f5a839bd9ab5f96497ca589d34d1f6a1271d882a60c9bb52cf3cdbeb07f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dff798624f6d5d4b27f6c96df697e941054dc5ad410020502edc65db8d054c5a37f0f138cfe5a69e716748d8dc65a07f352848fd24a8f0f0d83136f834085aec1cd48ff86a8eb10649cbf5886a8eb10649cbf64022d31", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:36 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "0D7SZ3NDXXQ10K0Y1P2W" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "Yhw+pyNdxXVfOz+enqEPz5i4xubYpPznUk/Z7jiBcq/rnwK5Kwv0df5Ff+b2ROcL" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html; charset=ISO-8859-1" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "last-modified": "Tue, 21 Sep 2010 17:37:41 GMT" + }, + { + "etag": "\"4486-7c5a6340\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "2590" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "-1" + } + ] + }, + { + "seqno": 162, + "wire": "88e50f0d836dc65fdc6196df697e94132a6a225410022502e5c64171b0298b46ffead7d66c96df697e94132a6a225410022502e5c64171b0a98b46ff55857d9780273f7f0fadd4e6d4ac4db973ebce7ebc6a92bd5eb149bede47cf91c8f5f636d4c5bec929c86e63f5931f0d71fbe71450c107d5d4", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "5639" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 23 Oct 2012 16:30:50 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 23 Oct 2012 16:30:51 GMT" + }, + { + "age": "938026" + }, + { + "x-amz-cf-id": "O6Rt-cRJLPLokVndpOyGdTuWoLI6bPqiRt_TrdmIiYayIHUPbzY__A==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 163, + "wire": "88e90f0d840b4fbef7e06196df697e94640a6a2254100225040b827ae01f53168dffee6c96df697e94640a6a2254100225040b827ae01f53168dffdcdb5585642f3ef3bf7f02aecf4a76357b1cd8bb2c9b40de1a30038d566afc3760439d1f9b7dd389e7ff70d9af161b3dc397572e9b21bfe2083fd9d8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "14998" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 30 Oct 2012 20:28:09 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 30 Oct 2012 20:28:09 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "318987" + }, + { + "x-amz-cf-id": "LmtQ4CHgGq-tu05FlE0VnrOXiq0ALsXRzmG89ZFrPGFrzAJOWjQADw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 164, + "wire": "88f30f0d836d97c3e46196df697e94640a6a225410022504cdc13b71a0a98b46fff2dfde6c96c361be940bea6a225410022502edc69ab8d36a62d1bf5585640f082dff7f02adbafbceb5926d24e4e5fa223781655adf32638b2ede742de76c3f94c8b61f2245fd9d2967deaa75438b7390c107dddc408725453d44498f57842507417f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "5391" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 30 Oct 2012 23:27:41 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 19 Oct 2012 17:44:45 GMT" + }, + { + "age": "308215" + }, + { + "x-amz-cf-id": "B9874IgNcW6Dl_iw2J-uxdH_JRYl-xRAXmd-Fx2sDQjm3zOmOAGS6A==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 165, + "wire": "88f80f0d840b4e3c2fe96196df3dbf4a002a693f750400894002e36edc1054c5a37ff7e4e36c96df697e94640a6a2254100225041b8d35702da98b46ff5585105c6996ff7f03adcba4ddf4f2d72e8a2ff8ef9024b2baab1c29ddfaffaabb8160f5cba756d673532fa5abc29676801263d7b2083fe2e1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "14682" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 00:57:21 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 30 Oct 2012 21:44:15 GMT" + }, + { + "age": "216435" + }, + { + "x-amz-cf-id": "JNivNWPfMlDwvI1crpnpaAtSZ9ynv0-1kJNOR3Kmfy-pFt3R00dHPQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 166, + "wire": "885f87352398ac4c697f0f0d840b4d38f7ee6196df3dbf4a002a693f75040089403b7000b811298b46ff7685dc5b3b96cf6c96e4593e94134a6a225410022500cdc6c5700253168dffebea55850bed38e35f7f04adb628f74ad7593a10cdc3ea9bba386ea87a6f46efabbd74696bbd148fc3918bd63d83cdd4b79c8f51fb7e6c820fe8e70f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "14468" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 07:00:12 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 24 Oct 2012 03:52:02 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "194664" + }, + { + "x-amz-cf-id": "u_bSf4kdjci5AymBMUSnaNCb7yBkMN4vlmaw6b2yHQaKkeC6bOoqXQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 167, + "wire": "885f87352398ac5754df0f0d840bac89fff46196df3dbf4a002a693f75040089403b7000b8dbaa62d1bfc3efee6c96c361be94101486bb1410022502fdc65fb8c814c5a37f55850bed3817ff7f03ad7b4d1b62d4ee3bd1c7ce4711cb9fe2078b2793e9d3f8cf5a8ecfe8f275edda6a71f3eacd8eeb0528be29e1820fedec", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "17329" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 07:00:57 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 20 Apr 2012 19:39:30 GMT" + }, + { + "age": "194619" + }, + { + "x-amz-cf-id": "8NlR_O7HCbbYd6sWYXsaGIxoNNX3kno3ZaIkqqgmHYk3r7P0msD2hA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 168, + "wire": "88c20f0d836c4d3ff86196df3dbf4a042a6a2254100225042b8d3b700d298b46ffc7f3f26c96df3dbf4a05953716b504008940bf704e5c69b53168df55850bed884d8b7f02acd6cbce0646e4f6ef6c5bc2cf2cbc78211c40b3de62e387625ade9f53cdd31fef5bf3dfc38ca7bb59bc921820f1f0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "5249" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 11 Oct 2012 22:47:04 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 13 Sep 2012 19:26:45 GMT" + }, + { + "age": "1952252" + }, + { + "x-amz-cf-id": "P3861d5dz7qGT13WJVUssV0-8x_VFQt4TtyhgjHZkDhDFHeoBpixcA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 169, + "wire": "88ca6c96c361be94136a65b6a504008140bd7042b8d3ea62d1bf5f86497ca582211f7b8b84842d695b05443c86aa6fe85893aed8e8313e94a47e561cc581c132f3afbc10ff6496e4593e94085486d994101912807ee003704ea98b46ffef0f0d8313e067408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 25 Jun 2010 18:22:49 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=623879811" + }, + { + "expires": "Wed, 11 Aug 2032 09:01:27 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:36 GMT" + }, + { + "content-length": "2903" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 170, + "wire": "88d06c96c361be9413ca6e2d6a0801128005c002e36e298b46ff5f911d75d0620d263d4c795ba0fb8d04b0d5a7c3ed5893aed8e8313e94a47e561cc581c640d3e279d6ff6496df697e94138a6a2254101912820dc6dfb811298b46ff6196dc34fd280654d27eea0801128166e01ab8cbaa62d1bf0f0d830b4e3bc3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 28 Sep 2012 00:00:56 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=630492875" + }, + { + "expires": "Tue, 26 Oct 2032 21:59:12 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:37 GMT" + }, + { + "content-length": "1467" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 171, + "wire": "88d56c96df697e94640a6a225410022500cdc68371a714c5a37fc8c7f15893aed8e8313e94a47e561cc581c640d81c6dc77f6496e4593e9413aa6a2254101912800dc69db82694c5a37fc10f0d83682d83c6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 30 Oct 2012 03:41:46 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=630506567" + }, + { + "expires": "Wed, 27 Oct 2032 01:47:24 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:37 GMT" + }, + { + "content-length": "4150" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 172, + "wire": "88d86c96df697e941094d03f4a0801128072e341b8cbaa62d1bfcbcaf45893aed8e8313e94a47e561cc581c10596d9742e7f6496df697e940b2a65b685040644a019b817ee36ca98b46fc40f0d830b6f03c9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 22 May 2012 06:41:37 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=621353716" + }, + { + "expires": "Tue, 13 Jul 2032 03:19:53 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:37 GMT" + }, + { + "content-length": "1580" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 173, + "wire": "88db6c96e4593e940baa65b6a504003ea05eb8272e34fa98b46fcecdf75893aed8e8313e94a47e561cc581c0b2cbedb6eb5f6496d07abe94089486bb141019128005c69ab810a98b46ffc70f0d03383536cc", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 17 Jun 2009 18:26:49 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=613395574" + }, + { + "expires": "Mon, 12 Apr 2032 00:44:11 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:37 GMT" + }, + { + "content-length": "856" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 174, + "wire": "88de6c96df697e94640a6a2254100225002b8c8ae09c53168dffcbd0fa5893aed8e8313e94a47e561cc581c640db6ebedbbf6496e4593e9413aa6a22541019128172e019b8db2a62d1bf6196dc34fd280654d27eea0801128166e01ab8cb8a62d1bf0f0d8475a7dc6bd0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 30 Oct 2012 02:32:26 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=630557957" + }, + { + "expires": "Wed, 27 Oct 2032 16:03:53 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:36 GMT" + }, + { + "content-length": "74964" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 175, + "wire": "88de0f0d8365b71ad06196dd6d5f4a082a6a225410022502fdc65db820a98b46ffe35891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96e4593e940894dc5ad4100225022b8015c13ca62d1bff5585081f7dc65d7f1cade156be5a1fdf9817b9eb373bd8753b96ba66c42a706b90a9ed8e592244b8d3edd4ddbcee8937876267efb2083f7caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "3564" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 21 Oct 2012 19:37:21 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 12 Sep 2012 12:02:28 GMT" + }, + { + "age": "1099637" + }, + { + "x-amz-cf-id": "UnPWM9TK0CYPiYCFO7JpmgG2mEPdetqHfd_sfHtz7tBC7MdT1QthvQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 176, + "wire": "88e60f0d83684e3bd86196df697e94640a6a225410022500d5c6dbb811298b46ffeb6c96c361be940894d444a820044a05fb8db7700fa98b46ffc6c5558565d69f71cf7f04ad2743e5d9ee69f2e0cbb0d26fb6ec84c5c5dee597be289a9502736fec5f9fb8f156ce569dc68edd62a79f686083c3c20f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "4267" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 30 Oct 2012 04:55:12 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 12 Oct 2012 19:55:09 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "374966" + }, + { + "x-amz-cf-id": "cjoJQzghJEJQidTuBdcGV7vefvG_4fs26RZ_XZHGp3J47Hsqk_mYqA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 177, + "wire": "88ee6c96e4593e94642a6a225410022500ddc13371b794c5a37fdbe05a839bd9ab5893aed8e8313e94a47e561cc581c640d81c6c0cff6496e4593e9413aa6a2254101912800dc69cb820298b46ffdb0f0d85085b742f7fe0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 31 Oct 2012 05:23:58 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=630506503" + }, + { + "expires": "Wed, 27 Oct 2032 01:46:20 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:37 GMT" + }, + { + "content-length": "115718" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 178, + "wire": "88f40f0d830840d7e06196c361be940bea6a225410022502fdc69cb80694c5a37ff3cdcc6c96e4593e940094cb6d4a0801028215c08ae05d53168dff5585089d0be1737f06acbdca7674d38a734966be06b39efca51e8f41ecff894f3cfa6cca4cb8d1577cf255f92ad3cb4f7d298b04d041cbca", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1104" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 19 Oct 2012 19:46:04 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 02 Jun 2010 22:12:17 GMT" + }, + { + "age": "1271916" + }, + { + "x-amz-cf-id": "CWh3NmGhidrPUirYTJeaMy1q9wfohhNrJcJHsnvLdnXf-hfmvNt_Eg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 179, + "wire": "88f20f0d8371b75de46196df697e94132a6a225410022502edc6dfb8dbca62d1bff76c96df697e94132a6a225410022502edc6deb82714c5a37fd2d15583136c8b7f02ad9c5f8dd69bda8cee78ef5382fe9896c3d7ddbc3f19d4fcbfb8b53356dfd1f335a3e34f447ee5e251833510c107cfce0f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "6577" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 23 Oct 2012 17:59:58 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 23 Oct 2012 17:58:26 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "2532" + }, + { + "x-amz-cf-id": "h2X5ptCOi7LbCmEDN_-FkzuUX3O9fZGO3nRZaYiuaVmjsZJVea0KlA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 180, + "wire": "885f87352398ac4c697f0f0d03343639e96196d07abe941054d03f4a080112817ae36edc69953168df7685dc5b3b96cfd7d66c96df3dbf4a05d5340fd2820044a05cb8d02e34ea98b46f558475c7c42f7f04ac26fd33b2ecdc991ede7ee7045ea9d30cdb6caf1cc6cda7d74bd8f978e45acc667acbba9152697aded56ec820d5d4", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "469" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 21 May 2012 18:57:43 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 17 May 2012 16:40:47 GMT" + }, + { + "age": "76922" + }, + { + "x-amz-cf-id": "cTNh37gW3aRYzh0_ymNAgRrpHgiKNyjCHWwWepii3kfSm2mifkCOuQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 181, + "wire": "885f87352398ac5754df0f0d830b827bef6196df3dbf4a05d5340fd2820044a0017190dc6dc53168dfc3dcdb6c96df697e940b6a681fa50400894037700fdc65a53168df558465b7da6b7f03adcf30afc98b7fb3c4dc6f69f3d629d3bbb72770dd1559f974c4e133463d497bdd3fbcdc72be3e1eccd4b7a1820fdad9", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "1628" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 17 May 2012 00:31:56 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 15 May 2012 05:09:34 GMT" + }, + { + "age": "35944" + }, + { + "x-amz-cf-id": "Lg2DdGTzo_5b8Nxk_htSqW7FB2nLWjG6cKbaOt8zmZY66pVw8K4fCA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 182, + "wire": "88c20f0d03333536f36196e4593e940b8a681fa504008940bf71a15c69c53168dfc7e0dfc15586640e36dbee7f7f01ab0eac716cea6fc58f992ea388127d06587dbb1d39939fbede02274937e72f89e56ec271a3b76ea1d57e6820dd7f1d8fd06421496c3d2a1283db24b61ea4ff408725453d44498f57842507417f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "356" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 16 May 2012 19:42:46 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 15 May 2012 05:09:34 GMT" + }, + { + "age": "3065596" + }, + { + "x-amz-cf-id": "1OH_QkiX-oKt7sV0toMi-aqqotKtLvRU2cjdTLewhf5rcVlqqk1ODg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Miss from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 183, + "wire": "886196dc34fd280654d27eea0801128166e01ab8d014c5a37fcc4088f2b0e9f6b1a4583f9105f7777eb3a6ffe7e6c08330398758b97f4003703370ff12acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7eaf6b83f9bd0ea52feed6a67879297b86d521bfa14c9c613a9938df3a97b5693a9ab7eb3a9ab86d52fe0ce6535f0ba65356fda652ef0dca6bc7cd4d5a73a9c34e4535f0daa61c9a54bdab429a61e2a64d3bd4bf834297b4ef5376f854c7821535edc0a67d5794c5ab8a9ab7de53f94088f2b0e9f6b1a4585fb6e73f8ff75fad3279fce32eaf7fecb576c1afdb666bcdb1f3af5fbab87563f5de86ba4ecc59578ccde6f1dc124ade197fdc62dba899ff7b9384842d695b05443c86aa6fae082d8b43316a4fdd5f8b1d75d0620d263d4c7441ea0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dff798624f6d5d4b27f6c96df697e941054dc5ad410020502edc65db8d054c5a37f0f138cfe5a69e716748d8dc65a07f352848fd24a8f0f0d83136f834085aec1cd48ff86a8eb10649cbf5886a8eb10649cbf64022d31408cf2b0e9f752d617b5a5424d279a03a02b6f904b09b8dd582128967dc69d582179b9412940db8fff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:40 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "0D7SZ3NDXXQ10K0Y1P2W" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "Yhw+pyNdxXVfOz+enqEPz5i4xubYpPznUk/Z7jiBcq/rnwK5Kwv0df5Ff+b2ROcL" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "application/json" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "last-modified": "Tue, 21 Sep 2010 17:37:41 GMT" + }, + { + "etag": "\"4486-7c5a6340\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "2590" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "-1" + }, + { + "x-amzn-requestid": "070e59c2-25b7-11e2-9647-1185f0fe0569" + } + ] + }, + { + "seqno": 184, + "wire": "88cad8c9c8c7c6e5c50f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dffc4c30f138cfe5a69e716748d8dc65a07f3c20f0d83136f83c1c0bf7e9903a2765209c584dc6eac10944b471880b12579a1b62745189b", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:40 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "0D7SZ3NDXXQ10K0Y1P2W" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "Yhw+pyNdxXVfOz+enqEPz5i4xubYpPznUk/Z7jiBcq/rnwK5Kwv0df5Ff+b2ROcL" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "application/json" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "last-modified": "Tue, 21 Sep 2010 17:37:41 GMT" + }, + { + "etag": "\"4486-7c5a6340\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "2590" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "-1" + }, + { + "x-amzn-requestid": "0727fc26-25b7-11e2-bb20-cf84a5272b25" + } + ] + }, + { + "seqno": 185, + "wire": "885f88352398ac74acb37f0f0d8369e79b408721eaa8a4498f5788ea52d6b0e83772ff6196dc34fd280654d27eea0801128076e001700ca98b46ffdcf5f46c96df697e9403aa436cca080112806ae32ddc13ca62d1bf5584105e75df7f14adc959ec9706f4e1bde616fbb418e1ad6aeff4bf7b5b44e5466f1d4e3d58bbf7ef679dc1d1e03ebbf0bb462d9041f3f2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4885" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 03 Nov 2012 07:00:03 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 07 Aug 2012 04:35:28 GMT" + }, + { + "age": "21877" + }, + { + "x-amz-cf-id": "IporfETtFCxA5v41bAp-pDjDCP4cWlKwkoaOGvvvrxS1Mw1yvUBlGQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 186, + "wire": "88e10f0d830b6d33c26196df697e940854dc5ad410022502cdc002e000a62d1bffe05891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96c361be940094cb6d0a080102817ae045719754c5a37f558669b75f69e07f7f04ae59c4bdbb2f7d3767724fd4b367036db3a64e3daaab8e53618f0de5b66ec3e49db2f3b6efe59ef343cb9487d9041f7caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf7f1b8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1543" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 11 Sep 2012 13:00:00 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 02 Jul 2010 18:12:37 GMT" + }, + { + "age": "4579480" + }, + { + "x-amz-cf-id": "-6t8SJvNBh6dZt3rUiRrjIVqnnVJiFbFC-QSFxcqJYuBXrzKAWWdoQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 187, + "wire": "88e90f0d03353932ca6196d07abe941094d444a820044a081700cdc6df53168dffe86c96df3dbf4a019532db52820040a00371976e36ea98b46fc6c555850802171a0f7f04ad7bbf2f77f09a50176e0935ee925a3073fda26e70faa6f7290d7f9d0f66eeee6bd161a010e7b7365c513439a083c3c20f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "592" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 22 Oct 2012 20:03:59 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 01:37:57 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "1011641" + }, + { + "x-amz-cf-id": "8vWzDFif0eREdPSdflEYZlgYAymCWdiDYl8Kv7KC_Fl0ALuKJG_4ag==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 188, + "wire": "88ed0f0d830b8d39ce6196df3dbf4a05c530963504008940397021b8dbca62d1bfecc9c86c96e4593e940b2a6a2254100205040b8d3b704153168dff5586109b75b0b82f7f02ad676c9927d92e7d4ffe6ad9846d3fcff0e903ab69c97f253828905e4f8b1b5f8fbcf0336f97c4388d3f79764107c7c6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1646" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 16 Feb 2012 06:11:58 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 13 Oct 2010 20:47:21 GMT" + }, + { + "age": "22575162" + }, + { + "x-amz-cf-id": "3RdIhQfLO9XOQFa49YXot07-NIDImEld2xoGH4X9880KTfwAGihvfQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 189, + "wire": "886c96e4593e94642a6a2254100225042b8cbb71b0a98b46ff6496e4593e9403aa693f75040089410ae32edc6c2a62d1bf5f911d75d0620d263d4c1c88ad6b0a8acf520b409221ea496a4ac9b0752252d8b16a21e435537f858cd50ecf5f0f0d830b6fb958a7a47e561cc581975f7df07d295db1d0627d2951d64d83a9129eca7e94a6d4256b0bdc741a41a4bf6196dc34fd280654d27eea0801128166e01ab8d054c5a37f4087aaa21ca4498f57842507417f7f1a88cc52d6b4341bb97f", + "headers": [ + { + ":status": "200" + }, + { + "last-modified": "Wed, 31 Oct 2012 22:37:51 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 22:37:51 GMT" + }, + { + "content-type": "application/ocsp-response" + }, + { + "content-transfer-encoding": "binary" + }, + { + "content-length": "1596" + }, + { + "cache-control": "max-age=379990, public, no-transform, must-revalidate" + }, + { + "date": "Sat, 03 Nov 2012 13:04:41 GMT" + }, + { + "nncoection": "close" + }, + { + "connection": "Keep-Alive" + } + ] + }, + { + "seqno": 190, + "wire": "886c96e4593e94642a6a2254100225040b8276e34153168dff6496e4593e9403aa693f750400894102e09db8d054c5a37fc5c40f0d830b6fb958a7a47e561cc58197441781f4a576c74189f4a54759360ea44a7b29fa529b5095ac2f71d0690692ffc3c2c1", + "headers": [ + { + ":status": "200" + }, + { + "last-modified": "Wed, 31 Oct 2012 20:27:41 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 20:27:41 GMT" + }, + { + "content-type": "application/ocsp-response" + }, + { + "content-transfer-encoding": "binary" + }, + { + "content-length": "1596" + }, + { + "cache-control": "max-age=372180, public, no-transform, must-revalidate" + }, + { + "date": "Sat, 03 Nov 2012 13:04:41 GMT" + }, + { + "nncoection": "close" + }, + { + "connection": "Keep-Alive" + } + ] + }, + { + "seqno": 191, + "wire": "886196dc34fd280654d27eea0801128166e01ab8d094c5a37f7685dc5b3b96cf7f2e9107ee3dffb76186fe6c1fa1ccd60c577939ed7f2db4f5976edc8f789d8f3aed235799aaefefb6f369fcdc1d581fd596d719d6c7e7541b272ceff203e4eb56e44d79ee5cac13f88d7f9fec5a839bd9ab5f87352398ac4c697f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dffeceb0f138cfe5a69e716748d8dc65a07f3ea0f0d83136f83e9e8e7e5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:42 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "0ZHTZBAADKEZ1K4EGBW6" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "yJRRI8wh/xPuc4C3nBZz5KNXS1OE9OJu63P/XksiIWL9W09cknSsgC8WWr29GiDY" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "last-modified": "Tue, 21 Sep 2010 17:37:41 GMT" + }, + { + "etag": "\"4486-7c5a6340\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "2590" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "-1" + }, + { + "x-amzn-requestid": "0727fc26-25b7-11e2-bb20-cf84a5272b25" + } + ] + }, + { + "seqno": 192, + "wire": "88c3c27f02910fee3072699ddfad3b0e8e2dbececbf8fff17f02b4f18b46de99a70eb4b0769aed8fa227c9deb77f726b48b3e67af7b2bac1d9ff6c512cde75f024dbf066bd0feddf745fc87ce72a7ff0c1c00f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dffee", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:42 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "1ZH0W43SZ47AMV593QDH" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "wGMRjKh1Pt/o44qHjshIvp7ZIPt2LK8Cze7/o3+/lfgxPUcgTEKCAZBzlDIoLoet" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 193, + "wire": "887689bf7b3e65a193777b3f5f911d75d0620d263d4c795ba0fb8d04b0d5a70f0d03343532c3c7", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "452" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:04:42 GMT" + } + ] + }, + { + "seqno": 194, + "wire": "88bf5f87497ca589d34d1f0f0d03343830c4c8", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "text/html" + }, + { + "content-length": "480" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:04:42 GMT" + } + ] + }, + { + "seqno": 195, + "wire": "88e96c96e4593e94642a6a225410022502f5c65fb8dbca62d1bf6196c361be940094d27eea0801128215c102e34d298b46ff6496dc34fd280654d27eea0801128215c102e34d298b46ff4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb768344b2970f0d846996840f408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f55846d9032f75890aed8e8313e94a47e561cc581e71a003f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Wed, 31 Oct 2012 18:39:58 GMT" + }, + { + "date": "Fri, 02 Nov 2012 22:20:44 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 22:20:44 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "43420" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "53038" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 196, + "wire": "8bcb0f0d830b6d33f0d0cfeae96c96c361be94089486d99410021502cdc699b8d32a62d1bf5584101a00bf7f1fac726ca71b91eaef85269b8549df7ba71c7d2d5b076cf1f2ab1e6f0e172ebbf21b16670e49a7b5d5b74449a083e8e7", + "headers": [ + { + ":status": "304" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1543" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 03 Nov 2012 13:04:42 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 12 Aug 2011 13:43:43 GMT" + }, + { + "age": "20402" + }, + { + "x-amz-cf-id": "6gJoa6bOvFtigUntTCjVHju-EqLbWnHKw6eJPDdiGK6ocghu7-S_cg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 197, + "wire": "88d26c96df3dbf4a09a5340fd2820044a01fb8d33702153168dfcb7b8b84842d695b05443c86aa6fd15893aed8e8313e94a47e561cc581c1059138e3ccff6496d07abe940894cb6d0a080c8940bf71a7ee09b53168dfd70f0d8369e133f7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 24 May 2012 09:43:11 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=621326683" + }, + { + "expires": "Mon, 12 Jul 2032 19:49:25 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:42 GMT" + }, + { + "content-length": "4823" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 198, + "wire": "886196dc34fd280654d27eea0801128166e01ab8d3aa62d1bfd77f139105fc77b76b77662ddbf13a2c61d9af6ddf4003703370ff12acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7eaf6b83f9bd0ea52feed6a67879297b86d521bfa14c9c613a9938df3a97b5693a9ab7eb3a9ab86d52fe0ce6535f0ba65356fda652ef0dca6bc7cd4d5a73a9c34e4535f0daa61c9a54bdab429a61e2a64d3bd4bf834297b4ef5376f854c7821535edc0a67d5794c5ab8a9ab7de53f97f14b5378a21c469b616bc9e6399c24f2fea7b7e1f162f0e77bbbe6f3c183d1fec7eb537fed29cd33bc89d395ddd66bc5fedd1f36f017d7f7b9384842d695b05443c86aa6fae082d8b43316a4fd85f96497ca589d34d1f6a1271d882a60c9bb52cf3cdbeb07f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dff798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:47 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "0DHCSP7QGSTG72H1QPRB" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "iwlAGigQepIxbg6chfZtqXoGGw6vBTgxU/ol+ayO5+ttKg7WcjWBSrPG+7aY5Eey" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html; charset=ISO-8859-1" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 199, + "wire": "886196dc34fd280654d27eea0801128166e01ab8d3ca62d1bfde7f0592039a796f7b9c3cf77e5fddf98f0e0bbf8cffc47f04b5ff724dbcb46d6f5f7e8dc35eaaedaff5b23583f3d62bb572456c5e4ef3cf77b72fcb335a6dd92eefff7fbbb830f8b1dfc790f77ceec3ddc20f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dffc1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:48 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "06NWT8YAYSXDSXHFEBX3" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "+dgTelR5Pvj5ApOpupZ5c4EXyGBnWsp/CtTohBqWXrKuiSIBT+ZSU/92HDHIoBxS" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html; charset=ISO-8859-1" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 200, + "wire": "885f88352398ac74acb37f0f0d83132fb37f2788ea52d6b0e83772ff6196dd6d5f4a32053716b50400894006e36ddc13ca62d1bfe35891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96df697e940b6a5f29141000fa806ae001702fa98b46ff558513eebaeb807f14ad368c04370c1970a69874dbc27efbb95ba7666f1b1b73ce1d7c7d989ca4e83e19373b9afc5bb7341ef6335ec8207caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2393" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 30 Sep 2012 01:55:28 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 15 Dec 2009 04:00:19 GMT" + }, + { + "age": "2977760" + }, + { + "x-amz-cf-id": "iMEciUEJFtmANuUhvSWuNQKwQ56xFPVzicWdjaUIS7KD_SS41vr3pQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 201, + "wire": "88e60f0d03353939c66196c361be9413ca6e2d6a080112816ae045700fa98b46ffebc5c46c96df3dbf4a019532db52820040a00371915c0b8a62d1bf558564207196df7f04ac4e90822ceb6ae7cb02dd23a911d76a13c22aba975fb0e348a4c21161fd964f677b75f9dcf30fb86ecfe2083fc3c2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "599" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 28 Sep 2012 14:12:09 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 01:32:16 GMT" + }, + { + "age": "3106359" + }, + { + "x-amz-cf-id": "tN10_L-OYWE-jbnsbpustU_nkePz1Ht2dF12FZfdzo8SDh6xAzABhw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 202, + "wire": "88cb0f0d8365a6ddca6196df697e94640a6a225410022502e5c69ab8c814c5a37fefc9c86c96d07abe94038a436cca0801128015c0b571b754c5a37f558565913417bf7f02adaa1fdef676bc37807bbe3c7987070e51d34eae1937c84cf92fc97abc39c22f7aaf439a6dd2b5503c4b889b2083c7c6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3457" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 30 Oct 2012 16:44:30 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 06 Aug 2012 02:14:57 GMT" + }, + { + "age": "332418" + }, + { + "x-amz-cf-id": "nAZvrqCa80oBwwxAEUWbmmOUITdcLIDdCpFL12zOCAKgSf4n0wfGcQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 203, + "wire": "88cf0f0d83642eb5ce6196df3dbf4a01e5340ec50400894006e09ab8d814c5a37ff3cdcc6c96c361be940b6a6a225410020502d5c1377196d4c5a37f5586101d75d7df7b7f02ae3b5fb777ddf1c7362b2f6af30d25efef15d9256717e77f026b0459233b6cdb83c70cb1fe10582484dab47ad9041fcbca", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3174" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 08 Mar 2012 01:24:50 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 15 Oct 2010 14:25:35 GMT" + }, + { + "age": "20777998" + }, + { + "x-amz-cf-id": "o4ZBTBwVKGrCOxAmevzGBdf3GXvw24E_Ibo53uEwUJbXc2EdAiOMyQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 204, + "wire": "88f20f0d827001d26196dd6d5f4a32053716b504008940b371a6ee01d53168dff76c96df3dbf4a019532db52820040a00371966e34d298b46fd2d1558513ecb617837f02ad8b067a5d77f1df1df679d8f10951f86e5a2f07c5cb2d158f7397f0b9a45cbade1ecdde34fa26be17b0df6a1820cfce0f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "601" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 30 Sep 2012 13:45:07 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 01:33:44 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "2935181" + }, + { + "x-amz-cf-id": "_ELm77X7wvQxQ8ccnoUS-_woGWJlpaS6DF6N2WkCaQSwNycPUCFD4A==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 205, + "wire": "88d70f0d8365a69cd66196df3dbf4a01b5328ea504008940b5702edc0814c5a37f7685dc5b3b96cfd6d50f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96df3dbf4a040a612c6a08010a800dc13b71a714c5a37f5586138175a79b7b7f03ac5b375b3c3561ad26c90c416ad5161362c5bfc75b337a9fbf4f20d04acc0dfedde14f2df7dccf0d3e31966820d4d3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3446" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 05 Jan 2012 14:17:10 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Thu, 10 Feb 2011 01:27:46 GMT" + }, + { + "age": "26174858" + }, + { + "x-amz-cf-id": "-Kkrw4riucQdic2OO_FiGGTwkrKyhvjx0Mcpi0Tz7UmWTD6LAmwHeg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 206, + "wire": "88dc0f0d8365d7d9db6196c361be94138a6a225410022502fdc0397191298b46ffc2dad96c96df697e9403aa436cca0801128066e01eb800a98b46ff558571c7da7dcf7f02adfc724f5b16eb7f25b49aae1db1794e25bbfabee97f5d02397e4cb509ba9e58019bac991ed58342da2ba0b34107d8d7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3793" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 26 Oct 2012 19:06:32 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 07 Aug 2012 03:08:01 GMT" + }, + { + "age": "669496" + }, + { + "x-amz-cf-id": "X6dyQ-kDIuminUqGxtG-vyD7eZ70sWXg-ltBtWE0KkdI8OEM-Mpleg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 207, + "wire": "885f87352398ac4c697f0f0d03363039e06196df697e94136a6e2d6a0801128172e00371b794c5a37fc7dfde6c96df3dbf4a019532db52820040a05bb8db5702d298b46f55866596de7dd07f7f03ad8e2ae191e2fe3563d8f9919e9c59927cd6f5cdd65f192ce7ed6f7cbb63e86ae5d14cf9776089d783384fb2083fdddc", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "609" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 25 Sep 2012 16:01:58 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 15:54:14 GMT" + }, + { + "age": "3358970" + }, + { + "x-amz-cf-id": "b_pAd8eX4r8HYc3jV3dhKukKkfwIrYz-zWqHjipfMmhJSE_781h1oQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 208, + "wire": "88e50f0d8365c6d9e46196e4593e940054c258d4100225002b8d39702d298b46ffcbe3e26c96d07abe941054d27eea08010a816ae059b827d4c5a37f5586132f3ccb616b7f02ad8fa27bd39f16b7333e293249c61724b7a09ef53f9eb66765eab2e8eba6f569d5c31136adde3a36b37e63cd041fe1e0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3653" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 01 Feb 2012 02:46:14 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 21 Nov 2011 14:13:29 GMT" + }, + { + "age": "23883514" + }, + { + "x-amz-cf-id": "bjtvmLGP6K92dIdVA6duj28yhxkrL38nJMkNCptOUGcR-vblR3Dgog==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 209, + "wire": "88e90f0d83644e3de86196df697e94132a6a225410022502e5c08ae32f298b46ffcfe7e66c96d07abe941054d27eea08010a816ae059b8d3aa62d1bf55857d97c2c83f7f02add3d3f2f19cdf7d6927ae99347135db13efb5515fc097432e1a3a66f525fa6216abd7e1d7970688666fcd50c107e5e4", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3268" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 23 Oct 2012 16:12:38 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 21 Nov 2011 14:13:47 GMT" + }, + { + "age": "939130" + }, + { + "x-amz-cf-id": "NjXCi6TD-dhpmdMViBrtzqn_DEt71fFljKydDm_2OCDAPJEMAg5xnA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 210, + "wire": "88ed0f0d8313eeb5ec6196df697e94640a6a225410022500fdc69db81714c5a37fd3ebea6c96df697e94032a435d8a0801128105c102e05d53168dff558565b75a6c5f7f02adfd89a7d1d4fc0d45de2e4c2aa7cdc32feea0dedfdfdf5d7c31df8b75fe1e6cdd384a991ec5b99c3d6ae346c820e9e8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2974" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 30 Oct 2012 09:47:16 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 03 Apr 2012 10:20:17 GMT" + }, + { + "age": "357452" + }, + { + "x-amz-cf-id": "Z_49skoUilBV6g2nhKUJZO1CTvzkPUHD_SDUxrSh1etd8GS3FknVlQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 211, + "wire": "88f10f0d83644d39f06196c361be94138a6a225410022500d5c037704ca98b46ffd7efee0f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96df697e940094c258d410020504cdc13971b0298b46ff5585744cb6e37f7f02ad06e8f75149c27d9b4383f47aede9749675a85656e2d97c5598bcb5f4db96dfda7887f5c8f9f8f417567764107fedec", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3246" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 26 Oct 2012 04:05:23 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Tue, 02 Feb 2010 23:26:50 GMT" + }, + { + "age": "723565" + }, + { + "x-amz-cf-id": "0SbSlmo1oQR1EZaPujBcrkn2rp6-JwnKeWPjRJuZmV1Z6bYwy17-7Q==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 212, + "wire": "88f50f0d8365c75df46196d07abe941094d444a820044a099b8d3d719754c5a37fdbf3f26c96d07abe941054d27eea08010a816ae05ab81794c5a37f55857df782e87f7f02afeb93f3f64879b786aae72dad39f9799ebbaed196fbf77df3b29cfcb793161fcd182eef6b8e7fcf5a6b28776bf1041ff1f0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3677" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 22 Oct 2012 23:48:37 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 21 Nov 2011 14:14:18 GMT" + }, + { + "age": "998171" + }, + { + "x-amz-cf-id": "kIXZdAY5Fnpheu46XC3kSBlJD9BzYrmLWTcGFXMEBT4VLXyNpe1SPw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 213, + "wire": "885f88352398ac74acb37f0f0d83138007408721eaa8a4498f5788ea52d6b0e83772ff6196e4593e940b4a5f2914100215040b8215c6db53168dffe15891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96e4593e9413ca6e2d6a08010a807ae005719754c5a37f558613c06d9742cf7f06aee9247a5f2b4cfada0e99acb357ef0ab17ee4e188c2c57b2e3dd999a85c730efd2cfe6f77f3d2db7a3dabb69d90417caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2600" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 14 Dec 2011 20:22:55 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 28 Sep 2011 08:02:37 GMT" + }, + { + "age": "28053713" + }, + { + "x-amz-cf-id": "jdbN9e43yR0jKrrOZUnGZIUGi2GCJHSK3n2VKaDm3XT7Xy-Rj8OqNQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 214, + "wire": "88e00f0d830b4f3fc66196e4593e940bca65b68504008940b57197ae01b53168dfe9c5c46c96c361be94034a65b6a50400814006e322b8d894c5a37f55857d9136e0197f04adfc678f8d95f2ab46bc7b2f3ba32edf0e6f5af8eee4ba3a111df0538b8b5a667fd63e0dcd74bd3a425f60921820c3c2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1489" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 18 Jul 2012 14:38:05 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 04 Jun 2010 01:32:52 GMT" + }, + { + "age": "9325603" + }, + { + "x-amz-cf-id": "X3VwQpWnMPHQC7MJRw6T-DaBIBalsbD0mGV4Ng9yHU5gBejjAez0dA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 215, + "wire": "886196dc34fd280654d27eea0801128166e01ab8d3ca62d1bfed4088f2b0e9f6b1a4583f910de0f2683a7937bf9ddc7772e9fcbaee2f4003703370ff12acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7eaf6b83f9bd0ea52feed6a67879297b86d521bfa14c9c613a9938df3a97b5693a9ab7eb3a9ab86d52fe0ce6535f0ba65356fda652ef0dca6bc7cd4d5a73a9c34e4535f0daa61c9a54bdab429a61e2a64d3bd4bf834297b4ef5376f854c7821535edc0a67d5794c5ab8a9ab7de53f94088f2b0e9f6b1a4585fb38469b7d17be536de098dc60523f9e3cb959c1fbe33b5b0cd9e1d2d33292afdde8ff64bb2d72ed6fccfcbf1a3b846a1c3d3a9ff7b9384842d695b05443c86aa6fae082d8b43316a4f5a839bd9abea0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dff798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:48 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "1C1W41NW5TYBHBJNXB7G" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "AatuyevJiRUtb6/2d9LbJJ3EZwL4Qi5oAN43fcnZTs+cBfpfR5xhWX4o6c4AFjko" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 216, + "wire": "88c4f37f04900803a1d06fca0fccd7f6c2c875bffbcbc37f03b47b63cd72e9c3d35dbcd57a92f5fcf59804d538b44d2d37aac22e9464911d6aeffba736cc9eb84e8fca0f96b06ce7927d7930e1f1c2c1ed0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dffc0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:48 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "101M70TJ0XKDRA31P9ZW" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "8Q84WjUy4qxnCmekXyK0cOh2MgfmCnF2jlIdsknvZNKQIyUhsXloJp0QYIhPIFFw" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 217, + "wire": "886196dc34fd280654d27eea0801128166e01ab8d3ea62d1bff67f019106d7797eb9c7601acd7f6076e6bbbafe6fc67f01b535ef0def92a08784ce6ca825d496add50ffbcb835936f9a1b73df027e658796dd8effbab00bf72b54b7f9c1dbfca3b396efc732f4fc5c45f96497ca589d34d1f6a1271d882a60c9bb52cf3cdbeb07f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dffc4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:49 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "0R7WZ6VQ04KDQ1RKBSDK" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "iCw5Tdn11Ug6Qn1eOt4uOA+JEPcRxl56zUcXJAWRQ7+nE2ZJ4m5XU7DWbrWSX6Jj" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html; charset=ISO-8859-1" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 218, + "wire": "88d80f0d8369d701d76196c361be940bea6a225410022500fdc0bf719654c5a37f7685dc5b3b96cfd7d66c96c361be9413aa65b68504008940b571915c644a62d1bf55850b207db0bb7f10adf1545d5ec8d4cf39edff3c197f2ea8a933dfb5796fb67c54adcf0664f6adf8fdece04cfdc6e7a7c7af9729a083d5d4", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4760" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 19 Oct 2012 09:19:33 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 27 Jul 2012 14:32:32 GMT" + }, + { + "age": "1309517" + }, + { + "x-amz-cf-id": "wn_k8I4g86z9xU39JO_mi8Znx5qLGm-YEKtqp9bzQUcLva6y9aPWWg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 219, + "wire": "88dd0f0d8365969edc6196c361be940b4a6e2d6a080112816ae32e5c0094c5a37fc2dbda6c96c361be941014cb6d0a080112816ae32f5c0054c5a37f55856990b4d89e7f02adc38218b4fe13746d7398dbfbdfc443ec7b9fbe3a8d4adbddfd3e2f1774962593769671fa8f26fe93f40c7e2083d9d8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3348" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 14 Sep 2012 14:36:02 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 20 Jul 2012 14:38:01 GMT" + }, + { + "age": "4314528" + }, + { + "x-amz-cf-id": "FEA_NXcSb4YgiTvDGcoQ8YzVOim-T7ZoGwBNe_-tBm3HybITjhj1bw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 220, + "wire": "885f87352398ac4c697f0f0d03353837e16196dc34fd282029a889504008940b77196ae09e53168dffc7e0df6c96df3dbf4a019532db52820040a05bb8db5702e298b46f5585088007042f7f03adef4e59d1d59c92f4f78b16c861338d16ebdaedce0ef91e77778c639f7e0e76ddc34536229345279fd5b243041fdedd", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "587" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 20 Oct 2012 15:34:28 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 15:54:16 GMT" + }, + { + "age": "1200622" + }, + { + "x-amz-cf-id": "vmJhsk3IfjzGGQAAi64eB8PuL0vI87SwHahTEYuBFlmrsmi_dxZ-IA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 221, + "wire": "88e60f0d836dc7c3e56196df697e9403ea6a225410022500d5c65bb826d4c5a37fcbe4e30f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96df3dbf4a05953716b504008940bb71a7ae05b53168df5585105f036e377f02ade06b496559b4f992c1a1976bc923ded64dec78b4ebe58747ee5fccfab279d70a28880f60c3b53e9fc3e7f61820e2e1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5691" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 09 Oct 2012 04:35:25 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Thu, 13 Sep 2012 17:48:15 GMT" + }, + { + "age": "2190565" + }, + { + "x-amz-cf-id": "UiucrnKNxdras37pId8z-tCHGNPWFMZJXLOIxPAsl_08EFRty9FxZA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 222, + "wire": "88ea0f0d8369d71ae96196df697e940b8a6a225410022502e5c65ab826d4c5a37fcfe8e76c96df3dbf4a01a535112a0801128015c0bb719694c5a37f55850b6d09c1377f02aded6f79ca88f8f0afb02fb5db1feeb368fde1167bb79af30e08ead39db4e99729c7ee1c1a6a785e2be9ab0c3041e6e5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4764" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 16 Oct 2012 16:34:25 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 04 Oct 2012 02:17:34 GMT" + }, + { + "age": "1542625" + }, + { + "x-amz-cf-id": "quvhesbVUpq0D4qHZPiMZU_LBC4xAEbnNL5tNfJoazAENn82wpjOFA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 223, + "wire": "88ca0f0d03353838ed6196c361be9413ca6e2d6a0801128115c65bb8cbea62d1bfd36c96df3dbf4a019532db52820040a00371a0dc1094c5a37fedec55856421105b0f7f02aedba52ccd975f5c7caee9dbbd1cf6ee058b76c19787bc65cde01e247b3ede03cd4ebe9b4f7624f9a7be6ea09a083feae90f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "588" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 28 Sep 2012 12:35:39 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 01:41:22 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "3112151" + }, + { + "x-amz-cf-id": "RNt3gJPkHWBNRTsYRS0r-qEJUzHeKw0wd8LRUaKmPjRoB_txmvKk0g==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 224, + "wire": "88f20f0d8365c79df16196e4593e94136a65b68504008940b771a66e34e298b46fd7f0ef6c96c361be940b2a65b68504008940b571b05c03ea62d1bf558679d0b8f38d7f7f02aed7171a397a8afe47763f46aeec869ce7265f38aee2bda99f3161a268ba5f9e1f1abbeed5463f33cbd7a353c3041feeed", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3687" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 25 Jul 2012 15:43:46 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 13 Jul 2012 14:50:09 GMT" + }, + { + "age": "8716864" + }, + { + "x-amz-cf-id": "P_VlWy_DI7Q9lOv31mLocJxGBGCO3x_Flg_jDhAwOvSOlHxhfkj4hA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 225, + "wire": "885f88352398ac74acb37f0f0d836db69d408721eaa8a4498f5788ea52d6b0e83772ff6196e4593e941054d03b14100225040b800dc0894c5a37ffdd5891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96e4593e941054d03b1410022502d5c69ab807d4c5a37f55860bedbcebc17b7f06ad9b8edfc45ea316eccf3b82f07d3983f160747ef6a5aee77dbd3856b6cfc92ccdcadb3884b7f26d1da06af788207caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5547" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 21 Mar 2012 20:01:12 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 21 Mar 2012 14:44:09 GMT" + }, + { + "age": "19587818" + }, + { + "x-amz-cf-id": "gVRXsClGSK87EC1y6EX-0j9CO-BL95NF-urXdrKWurV1eDIRau04Cw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 226, + "wire": "88c70f0d83680207c66196c361be940bea6a225410022500fdc0bd702da98b46ffe5c5c46c96dc34fd281654d444a820044a08371a15c6c0a62d1bff55850b207db7db7f04ada7bd791eedcbbe2c73c516f3f4e5cf23bc79d2418fc57944ec7555af281e3fbecef0a33fe65373f4485a9a083fc3c2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4020" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 19 Oct 2012 09:18:15 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Sat, 13 Oct 2012 21:42:50 GMT" + }, + { + "age": "1309595" + }, + { + "x-amz-cf-id": "mvpI8qWvGHh__TojWYI7VYmcaawpJ27bnnPJ08ozq7UlLXJiYycA4g==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 227, + "wire": "88cb0f0d8369d71fca6196d07abe9403ca6a2254100225041b817ae32da98b46ffe9c9c86c96df697e94642a65b685040089403d71a7ee32ea98b46f55851082e3aeb77f02ad91ec593f43909bb97ee8bbf995ec68fd8bba50d7a46dc16e2c28d366abb0c46bad8ed753c357ef6b4bdb66c820c7c6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4769" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 08 Oct 2012 21:18:35 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 31 Jul 2012 08:49:37 GMT" + }, + { + "age": "2216775" + }, + { + "x-amz-cf-id": "d8GIZ1IcSWZMBXJ8HsZ_vts4ysREuGFsNrOBA_iB5au7tUOZqueqQQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 228, + "wire": "88e40f0d830badbfce6196df3dbf4a09d53716b50400894006e09db82714c5a37fedcdcc6c96d07abe940894ca3a941000fa817ae05fb8cb8a62d1bf5586644cbce34d7f7f02ab8f7caeb538bf53b1316403e9736d3647ce659f74f1634a536a34e344eb68c51bd37085b0c3cdddfe26820fcbca", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1759" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 27 Sep 2012 01:27:26 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 12 Jan 2009 18:19:36 GMT" + }, + { + "age": "3238644" + }, + { + "x-amz-cf-id": "bTf74h2ZtQt_I09t6RmrbYg-97o_HtttusNHsh-MGb8gUA51AY7Twg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 229, + "wire": "88d30f0d840baeb41fd26196c361be940894d444a820044a05ab817ee05b53168dfff1d1d00f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96e4593e940bea681fa5040081403f71b0dc682a62d1bf55860bcfb8cb2f7f7f02aee1cfcbe197972e84d58f364d42f1cc4e70d3ce56f06ce7cf9353379396ace3f5e968ac673ddbdd366c47173c4107cfce", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "17741" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 12 Oct 2012 14:19:15 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Wed, 19 May 2010 09:51:41 GMT" + }, + { + "age": "1896338" + }, + { + "x-amz-cf-id": "UYx91fWWjcOHKIO2wY26UNYf5EQYYW4g5IWOLayy-_r3LBCjQQsV6w==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 230, + "wire": "88f46c96d07abe94138a681d8a0801128205c0bf700f298b46ff5f86497ca582211f7b8b84842d695b05443c86aa6f5a839bd9ab5893aed8e8313e94a47e561cc581c640e36e32c8bf6496df3dbf4a09e535112a080c8940bf704cdc69b53168df6196dc34fd280654d27eea0801128166e01ab8db2a62d1bf0f0d831044e7dd", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Mon, 26 Mar 2012 20:19:08 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=630656332" + }, + { + "expires": "Thu, 28 Oct 2032 19:23:45 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:53 GMT" + }, + { + "content-length": "2126" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 231, + "wire": "887685dc5b3b96cf6c96df697e940baa65b685040089403771a76e34da98b46fc5c4c35893aed8e8313e94a47e561cc581c13afb4e38e35f6496d07abe9413aa6e2d6a080c894082e342b8cbaa62d1bfc20f0d830b617be1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Tue, 17 Jul 2012 05:47:45 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=627946664" + }, + { + "expires": "Mon, 27 Sep 2032 10:42:37 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:53 GMT" + }, + { + "content-length": "1518" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 232, + "wire": "88c16c97e4593e94034a693f7504003ea01cb8db571a694c5a37ffc8c7c65893aed8e8313e94a47e561cc581c0b2cbedbaeb3f6496d07abe94089486bb141019128005c69db8d38a62d1bfc50f0d83109e7be4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 04 Nov 2009 06:54:44 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=613395773" + }, + { + "expires": "Mon, 12 Apr 2032 00:47:46 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:53 GMT" + }, + { + "content-length": "2288" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 233, + "wire": "88c46c96c361be941054dc5ad410022500edc643700253168dffcbcac95893aed8e8313e94a47e561cc581c138fbec81c0ff6496df3dbf4a05c53716b5040644a01fb8d3f702d298b46fc80f0d8313ad8be7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 21 Sep 2012 07:31:02 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=626993061" + }, + { + "expires": "Thu, 16 Sep 2032 09:49:14 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:53 GMT" + }, + { + "content-length": "2752" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 234, + "wire": "88c76c96d07abe9413ea6a225410022500d5c0b3704ca98b46ffcecdcc5893aed8e8313e94a47e561cc581c640d01e79f73f6496d07abe94136a6a22541019128215c65fb8d3ea62d1bfcb0f0d840bc269ffea", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Mon, 29 Oct 2012 04:13:23 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=630408896" + }, + { + "expires": "Mon, 25 Oct 2032 22:39:49 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:53 GMT" + }, + { + "content-length": "18249" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 235, + "wire": "88eb0f0d03373936ea6196c361be940894d444a820044a04171a6ee05f53168dffcbe9e80f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcfd555850be07c2eb57f15ad7f4632072febbd76bbf9487ac4073174fcfcbbd344a88f9a23afce36da7f8503de7bfe19d69d546aaf2d534107e6e5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "796" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 12 Oct 2012 10:45:19 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Wed, 19 May 2010 09:51:41 GMT" + }, + { + "age": "1909174" + }, + { + "x-amz-cf-id": "9MHc1JZ7kR7Xm1k_06GjXXBjMfsbYsbpxH549UlaToDw3PtOlOpJng==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 236, + "wire": "886196dc34fd280654d27eea0801128166e01ab8d894c5a37fce4088f2b0e9f6b1a4583f90037cddd0db62db8b0f3c7c5ed1cd96834003703370ff12acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7eaf6b83f9bd0ea52feed6a67879297b86d521bfa14c9c613a9938df3a97b5693a9ab7eb3a9ab86d52fe0ce6535f0ba65356fda652ef0dca6bc7cd4d5a73a9c34e4535f0daa61c9a54bdab429a61e2a64d3bd4bf834297b4ef5376f854c7821535edc0a67d5794c5ab8a9ab7de53f94088f2b0e9f6b1a4585fb23e2e59b6d41e5fefa74255c470d28d9aedbf9e9017b510deaf26fb35331c3388fcb78b8d85ae5f7463c3436816e70af0e76f7b9384842d695b05443c86aa6fae082d8b43316a4fd65f96497ca589d34d1f6a1271d882a60c9bb52cf3cdbeb07f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dff798624f6d5d4b27f4085aec1cd48ff86a8eb10649cbf4086f2b2075ad5cd91ae73a4f148645740fd447aa2f058d064975886a8eb10649cbf408bf2b4b60e92ac7ad263d48f89dd0e8c1ab6e4c5934f64022d31", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:52 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "05Y7M552RGFYHV8MY341" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "oGWKRn1W+jjcnVaAmsQPuDLm0eqlACpITrO3bAh2oWT2VrepfzlHFl5s2S6e8ah5" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html; charset=ISO-8859-1" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "pragma": "no-cache" + }, + { + "x-sap-pg": "photo_display_on_website" + }, + { + "cache-control": "no-cache" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "expires": "-1" + } + ] + }, + { + "seqno": 237, + "wire": "885f88352398ac74acb37f0f0d83132dbd408721eaa8a4498f5788ea52d6b0e83772ff6196d07abe941054d27eea08010a816ae341b8d854c5a37fdc5891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f0f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96d07abe941054d27eea08010a816ae059b827d4c5a37f55866400702cbc2f7f12adb36a4fbec2eb360d95732de08a78b16ffce77b8cb9269fc26c663ddefb37cf653b05fb8bd729915f19fbec820f7caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2358" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 21 Nov 2011 14:41:51 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Mon, 21 Nov 2011 14:13:29 GMT" + }, + { + "age": "30061382" + }, + { + "x-amz-cf-id": "rRtoTrePiEQnYeC12h_GTXYCVfIghwtr3bSzq5YQmQ2ZGyWgspVhvQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 238, + "wire": "88c70f0d83132d33c66196d07abe9413ca693f750400854002e320b8d054c5a37fe4c5c46c96df697e94038a435d8a0801028266e04171b794c5a37f558613ed81d71b177f04ad69de908b47eb5bbc357ef1bae9171f431f2a7c6fc74f6fc66ce02d1e37974cee94397dc5c0fc35d7cec1ec820fc3c2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2343" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 28 Nov 2011 00:30:41 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 06 Apr 2010 23:10:58 GMT" + }, + { + "age": "29507652" + }, + { + "x-amz-cf-id": "47jA2MZ4Sw4DCikN2VyaaWmwTHmqX3rU2MwTeNh7e1Jz_UoUPpYraQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 239, + "wire": "88cb0f0d83640dbfca6196df3dbf4a01b5328ea504008940b3704e5c13aa62d1bfe8c9c86c96df3dbf4a040a612c6a08010a800dc13b71a714c5a37f5586138175d7c0e77f02aee5cc7dfd64d84c309bf7a2cb7ecfd1a1fedbb65d5931f7a4b9d9e12fbef6dbb72e28888afd7efac1777fef7f1041c7c6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3059" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 05 Jan 2012 13:26:27 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 10 Feb 2011 01:27:46 GMT" + }, + { + "age": "26177906" + }, + { + "x-amz-cf-id": "WYavyIQcFAiZj--Zhj4aZuRfOIHvmeL3UfzvuuRJG_cspyZyEBTZvw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 240, + "wire": "88cf0f0d83081f07ce6196dc34fd280654d27eea0801128072e01bb800298b46ffeccdccc5558413617d9f7f01ad7b8df1bfbef6ddcef1cd28e1f327ad67d1e48cd5c1bc78e47563927bd174bbd296f1a6aa16e9d7475b77c3041fcac9", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1090" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 03 Nov 2012 06:05:00 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 06 Apr 2010 23:10:58 GMT" + }, + { + "age": "25193" + }, + { + "x-amz-cf-id": "8VDa9TCRS7VKfaAxdyPoMxc3nU5HHd7-ochC_jBjm5Htnl-jkMkuTA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 241, + "wire": "885f87352398ac5754df0f0d03373330d26196e4593e94136a65b685040089408ae342b8d854c5a37ff0d1d06c96d07abe94642a651d4a08010a807ae001700fa98b46ff558579d13ae8857f03add967e63c3efc187a3b349d3e1cf3ec3e62c3e9ed583ccbb3c2237cbce6d333fe5b3caffaab9ce63532f69a083fcfce", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "730" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 25 Jul 2012 12:42:51 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 31 Jan 2011 08:00:09 GMT" + }, + { + "age": "8727722" + }, + { + "x-amz-cf-id": "QrXHFzwiaMq4tNw6xz1x_Fy8OExfQwsb9eYgNg9x5of9ynYhiimfqg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 242, + "wire": "88d70f0d83089d0fd66196df3dbf4a01b5328ea504008940bf71b7ae084a62d1bff4d5d46c96d07abe941054d27eea08010a816ae059b8d3aa62d1bf558613816da65f0f7f02adf72da5d06d9da5d0e262c8ededc63ede5ef2b9946d0ef55b25f05d9fb4f9377edcff5ec7965abae22c90ec820fd3d2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1271" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 05 Jan 2012 19:58:22 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 21 Nov 2011 14:13:47 GMT" + }, + { + "age": "26154391" + }, + { + "x-amz-cf-id": "zfueMiQqfM6t_I7CSioRWzJ6Ja4aCnQfweQZmxivqYZ8HJfnkGedAQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 243, + "wire": "88db0f0d830b2cb3da6196e4593e9403ca612c6a0801128215c08ae09f53168dff7685dc5b3b96cfdad9ce5586132203ceb4d77f02adc19f062a6eded0c8ef2304578c9dd86df0dfec4f9f506ac9a7f43be47b862f03e4de5d9db54da66b23866f1041d7d6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1333" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 08 Feb 2012 22:12:29 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 10 Feb 2011 01:27:46 GMT" + }, + { + "age": "23208744" + }, + { + "x-amz-cf-id": "ELEGmBCM3aCsE_CitSFuw5Z_9oO1nINZ1Td8UGwaW5JQqOgNgrbAgw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 244, + "wire": "88df0f0d830b2e07de6196e4593e9403aa681d8a080112820dc68371a7d4c5a37fc1dddc0f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96c361be9403aa6a225410021500e5c102e32e298b46ff5586101d7c2cbcd77f02acaf576352dd7d74e796a01f37933b92f61c14e1c25ab84e0d71e78b6bf9e37e7737d76e24813b6b6bdbdb2083dbda", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1361" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 07 Mar 2012 21:41:49 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Fri, 07 Oct 2011 06:20:36 GMT" + }, + { + "age": "20791384" + }, + { + "x-amz-cf-id": "pOqim5pkNLfn0oKxi7ICFEmFFenUh0PbL_R9Lb9h6TpuGt0tRp4z8Q==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 245, + "wire": "88e30f0d83640073e26196df3dbf4a059535112a08010a8005c6dfb810a98b46ffc5e1e06c96df697e940094c258d410020504cdc659b820298b46ff558665969e032d0b7f02acc4fdeba0d780493bfbff27bdd53b3c4d91b47272ca3bf429b20e8e9d9a67686794d4fef788b4dfdfb1bd9041dfde", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3006" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 13 Oct 2011 00:59:11 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 02 Feb 2010 23:33:20 GMT" + }, + { + "age": "33480342" + }, + { + "x-amz-cf-id": "G9CB0PE2to9TXhCktQwgI5sW6rlvjeiIaljq43R1hfimZv_emDTQ5Q==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 246, + "wire": "88e70f0d83640d87e66196df697e941094d27eea08010a820dc03571a0298b46ffc9e5e4cd558613efb620059f7f01ae7ae8abdfc8d77d76358cb7cdebcfe5f191feb7bdfe67c00ed272f7a2836a77be473f1af60c9af0c6eeacbbbc4107e2e1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3051" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 22 Nov 2011 21:04:40 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 21 Nov 2011 14:13:47 GMT" + }, + { + "age": "29952013" + }, + { + "x-amz-cf-id": "8B2pTWiByqir35Y8C9JwI9kCzXLE0qdWzMliO7vI6X4z0IPFb7OJSw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 247, + "wire": "88ea0f0d8365d79ae96196df697e94134a651d4a0801128215c037700f298b46ffcce8e76c96c361be941054d03f4a0801028105c65ab8d34a62d1bf5586134d81b0bcdf7f02ac31bc1bb4c4e37b962889b51a075ee45e77dc69b6da01bdf2e9f4d69e90f5a38a23cb3fec73c7a75831f8820fe6e5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3784" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 24 Jan 2012 22:05:08 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 21 May 2010 10:34:44 GMT" + }, + { + "age": "24505185" + }, + { + "x-amz-cf-id": "iiwiqgcVCWG_cRsMapSsC7zbtuul0T9eNy4NjAklVsbJhZbhbNP0Hw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 248, + "wire": "88ee0f0d83134cbfed6196dd6d5f4a080a693f7504008540b371a7ae34053168dfd0eceb6c96e4593e9413ca6e2d6a08010a807ae005719754c5a37f558664016c0fbacf7f02add2cc576ee89f0f3659e711071e908ff4f2947b77f3e62ede66f3928f57fb76abd7bdf8cf7635b1ecd8d7586083eae9", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2439" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 20 Nov 2011 13:48:40 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 28 Sep 2011 08:02:37 GMT" + }, + { + "age": "30150973" + }, + { + "x-amz-cf-id": "N3_BBMhFY33Y_cabN1aZofeaRTYY2qxgxIlyDqqnyzTHoBb-HQQ4kA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 249, + "wire": "88f20f0d83640f3bf16196d07abe941094d444a820044a0417020b80654c5a37ffd4f0ef0f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96d07abe94136a65b6850400854106e01eb82754c5a37f5585081a744f877f02aeaf86e8b7f6d6dfea76eae38afe2b76ac5bff9e006e9ef41b17bf66f1ab4fe9b6783f2d54f8d1651beadbc0db2083eeed", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3087" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 22 Oct 2012 10:10:03 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Mon, 25 Jul 2011 21:08:27 GMT" + }, + { + "age": "1047291" + }, + { + "x-amz-cf-id": "pUS_TqP5ZtROVGDGuR-eDXw0ijzMiGzziwONZiQwoWOmwMrlTnRUiQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 250, + "wire": "88f60f0d8365975df56196d07abe941094d444a820044a04571b6ee01a53168dffd8f4f36c96c361be94036a65b6a504003ea05ab8d39700053168df558508197597c17f02ace0f6cf7468053ea1ab8a4b5557bd577b06065b878dda9fd6ef7e0f592f1bcb76932710bf6c585670341e1820f2f1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3377" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 22 Oct 2012 12:55:04 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 05 Jun 2009 14:46:00 GMT" + }, + { + "age": "1037390" + }, + { + "x-amz-cf-id": "U8QzlM0myAnVtennCypCEE35AVBn9P7vU8rfVC-qdIV19u_F-61loA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 251, + "wire": "885f87352398ac4c697f0f0d023433408721eaa8a4498f5788ea52d6b0e83772ff6196c361be9413ca6e2d6a0801128115c0b9702253168dffde5891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96df3dbf4a019532db52820040a05cb816ee32253168df5585642165910b7f06adf5b7462be1a3261b3ded74d7489ceeb7e68a38926bdcda3f66fbf9af83ddfd5de69e7927bf89af134b3cfe20837caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 28 Sep 2012 12:16:12 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:15:32 GMT" + }, + { + "age": "3113322" + }, + { + "x-amz-cf-id": "yRMGD1lIFrzR7iBctL75xllVcgCY4oq5vxpU8vyBYtYIhDG4wgfhhw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 252, + "wire": "885f88352398ac74acb37f0f0d83138cbfc76196df697e940854ca3a94100215022b8cb971a1298b46ffe7588ca47e561cc581c640e8800003c66c96df697e941054be522820040a081704cdc0bea62d1bff55866dd0bef34f8b7f06adcdc35acde5ab1ce959ae34bf0e46edce267bf2ed913be2d77a13effce73ee71de04dfcfd6333f8daec7a7c4107c5c4", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2639" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 11 Jan 2011 12:36:42 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 21 Dec 2010 20:23:19 GMT" + }, + { + "age": "57198492" + }, + { + "x-amz-cf-id": "KUP-5JnHht-4Vm9AI5uL23vWqItT_PCAoTXYhS67UcTYyHi9H4qomw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 253, + "wire": "88cd0f0d023533cc6196dc34fd2827d4dc5ad410022502e5c699b80714c5a37fec6c96df3dbf4a019532db52820040a099b8115c684a62d1bfcccb5585640103e07b7f02ad7eac31c26d0fdfafcde649543145ca397971c17b174fdebd66a6f3974360afb98e2aff7817565ae9275cde2083c9c80f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "53" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 29 Sep 2012 16:43:06 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 23:12:42 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "3010908" + }, + { + "x-amz-cf-id": "9nFbAiM9DpxC3cnA__WbfWVECGjZkkgmC6B1r2D6H_pZUeOJpmckKw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 254, + "wire": "88c70f0d83089c73d06196df697e940b2a5f2914100215001b8066e34ca98b46fff0cfce6c96d07abe940894be522820042a08571905c1094c5a37ff558613c203ee3a1f7f02ad6bf3735a7637d130eb47e58f5bedbcfbb24e64b3cc7cf3ea19259bcefec3cb253362e1bb8978fe339db178820fcdcc0f1397fe590ad61900e1a079e2dd9db0022ddb820045ff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1266" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 13 Dec 2011 01:03:43 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 12 Dec 2011 22:30:22 GMT" + }, + { + "age": "28209671" + }, + { + "x-amz-cf-id": "4XS4NQ5jtAPsXr8uz5LSIhit3YaYLOacfgxTqaJdmgGUSVeVX3L52w==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"31-ris0UMaL_SL500_SS100_#1\"" + } + ] + }, + { + "seqno": 255, + "wire": "88d50f0d03353439d46196d07abe940baa6e2d6a0801128072e04171a0a98b46fff4d3d26c96df3dbf4a019532db52820040a01ab8d06e05e53168df5586680f36e36cff7f02ad789a05823ddd85e741ff6986dec3dba6ac7c3f0edcb87075f1adf7d936a71ce5f9b5e26c9f9c9f83ef12d0c107d1d0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "549" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 17 Sep 2012 06:10:41 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 04:41:18 GMT" + }, + { + "age": "4085653" + }, + { + "x-amz-cf-id": "8cM2EbSq2xMoZmAuqaRNnHUXo5fFEkwP993iO66WXR8cQhYdXav_-A==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 256, + "wire": "885f87352398ac5754df0f0d8313207fd96196d07abe941094d444a820044a0017190dc69e53168dff7685dc5b3b96cfd9d86c96df697e940bca6e2d6a0801128266e00371a654c5a37f5585081e0bef397f04acc9159c91b7050b370ea8732495339cc4c35688741eaaf364f3bac0ddc461bb96bf58ac4df5b8e5a30c3e2083d7d6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2309" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 22 Oct 2012 00:31:48 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 18 Sep 2012 23:01:43 GMT" + }, + { + "age": "1081986" + }, + { + "x-amz-cf-id": "I_rWsREl-5AOAKtcn3LicFnMAMonpKIxSr1BGia7JpyGrtD-VJlFAw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 257, + "wire": "88c30f0d8369c79fde6196e4593e940814d444a820044a00171976e044a62d1bffc26c96c361be940b6a65b68504008540bf71a66e36fa98b46fdedd55851042f34e0b7f02acf796c0746bc907c6aed1a5dea5dc4f09a38907c77e8af36cf5aaac4e0a494ccc7785cea77f78707a7a764107dbda0f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "4689" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 10 Oct 2012 00:37:12 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 15 Jul 2011 19:43:59 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "2118462" + }, + { + "x-amz-cf-id": "zJr0j4xcaVnqbt7keScwtlVcaVTMpKQyOnG62dfi3bC2Yn7ZUU8hmQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 258, + "wire": "88c70f0d03333034e26196c361be94134a436cca0801128266e34f5c65f53168dfc6e1e06c96c361be940bca65b68504003ca08571b6ae34e298b46f5586700fb6ebadff7f02add79a9eb696bbfd6f74e9cdc142ebbeb65bcede092b6416ff4b44e5fa33530f79c9e3ded463f5537564b364107fdfde", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "304" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 24 Aug 2012 23:48:39 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 18 Jul 2008 22:54:46 GMT" + }, + { + "age": "6095775" + }, + { + "x-amz-cf-id": "PKmkuepDkCjjY62A77yQuYuUte5c2Ty-_6DlKmAvhcwzRsHyn5nIrQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 259, + "wire": "88e70f0d83642f33e66196df3dbf4a09d53716b504008940337040b8d054c5a37fca6c96df3dbf4a019532db52820040a05cb8205c644a62d1bfe6e55585644c85e6d97f02add5e21f0eb3ee52cd4f28b663e6b75478e4309168de3f16bcfeacd94bdf0fe47fbe65ec7cdaf0396d381ad9041fe3e2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "3183" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 27 Sep 2012 03:20:41 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:20:32 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "3231853" + }, + { + "x-amz-cf-id": "OwAw73zfegmW_QHY-kswWa1c-b8oV4xZ-5eevFXbZxfqoKPE6umE4Q==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 260, + "wire": "88e10f0d03383539ea6196df3dbf4a002a693f7504008940b77196ae09c53168dfcee9e80f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96df697e941094d03f4a0801128076e32ddc032a62d1bf55850b8cbc27bf7f02ada252ecbd4ec5b3ef414728a9cf4f85b0116d1360bdb51874fabe38dc3eabfab49fbe9ad9cd77c0f95ba764107fe7e6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "859" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 15:34:26 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Tue, 22 May 2012 07:35:03 GMT" + }, + { + "age": "163828" + }, + { + "x-amz-cf-id": "lfeQCmQ-LTseaf2mLmw-Ec-MgECRsFNyDab6oODONovNp3KBwaWuNQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 261, + "wire": "88e50f0d03393034eec1d1eceb6c96df3dbf4a082a435d8a08010a807ee360b80794c5a37fc07f00ad97cb5d6de789a7719330f1e9e47c38cfc2be36ec7d1c8735eab2b398fe56e50a311723f1e7db6e4c3d7f61820fe9e80f1397fe590ad61900e1a079e2dd9db0022ddb820045ff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "904" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 15:34:26 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 21 Apr 2011 09:50:08 GMT" + }, + { + "age": "163828" + }, + { + "x-amz-cf-id": "fx4kuYG47HcKaHNWoFHoUpVuQ9sWagCnJ3Kox-WAsGeI9bLRuIFkZA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"31-ris0UMaL_SL500_SS100_#1\"" + } + ] + }, + { + "seqno": 262, + "wire": "88e70f0d03393838f0c3d3eeed6c96e4593e94085486bb1410022500fdc69fb8d894c5a37fc27f00acae4f7146e3e976bec2c3e76f9c17d9caff6973f2b62e3467ab74c967875b39e41644729a7013cc248f6cd041ebea", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "988" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 15:34:26 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 11 Apr 2012 09:49:52 GMT" + }, + { + "age": "163828" + }, + { + "x-amz-cf-id": "pdz_b69t7pq2FxRxED3J9qfLWu_VlLnSgt3UkrYI2IsWgh0cxAcbRg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 263, + "wire": "88f30f0d8375a75df26196c361be940bea6a225410022502cdc03371a1298b46ffd6f1f06c96df3dbf4a019532db52820040a003700e5c0b2a62d1bf5585089f700e8b7f02afc95fe75f13e5c5cb863dba2af5b61d9c7279ed2cb90317ee0f5c96abdf0e766ee564b63db62e61b6dc5b6f5ed9041fefee", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "7477" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 19 Oct 2012 13:03:42 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 01:06:13 GMT" + }, + { + "age": "1296072" + }, + { + "x-amz-cf-id": "IpXkwhJGWUHRMnyRAQVIxqffI1_ZEyW-nzUYrSWrfr8R_Y1uuGRCCQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 264, + "wire": "88db0f0d827402f66196c361be940b4a6e2d6a080112816ee36cdc138a62d1bfda6c96e4593e940894d444a820042a0037190dc640a62d1bfff6f5558669903ef3cf7f7f02aec68c4e47949ef1f8f7d4c5c73514e1f7805b2f53ef3f9de3cfaff3f2a87d2da5b6d3ee3b7bd89fddb7bddef1041ff3f20f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "702" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 14 Sep 2012 15:53:26 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 12 Oct 2011 01:31:30 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "4309888" + }, + { + "x-amz-cf-id": "HsG6bJczHwzkieHglmFzE2QCmzLxTaLPXXnAy-N55tzbuvrtZRCzCw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 265, + "wire": "88df0f0d8375f799408721eaa8a4498f5788ea52d6b0e83772ff6196c361be940bea6a2254100225022b827ee09d53168dffdf5891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96c361be940b6a65b68504008540bf71a6ae044a62d1bf5585089f7822777f05ad45efcd5a62665eee72b63ceedb9f7651e4ed5f1a2f4dd3215397d770cf14fa24c2d13985d927e4dc29b764107f7caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "7983" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 19 Oct 2012 12:29:27 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 15 Jul 2011 19:44:12 GMT" + }, + { + "age": "1298127" + }, + { + "x-amz-cf-id": "sCXON_3fv6WubL7uLSJaIqpVlCgjIetJyv1h_hMdF4cY17dhW5AtuQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 266, + "wire": "88e80f0d84105b79dfc66196c361be940bea6a225410022500edc68571a7d4c5a37fe7c5c46c96df3dbf4a09d53716b504008940bd71a15c640a62d1bf55850b216d91377f04addef8f75bec98611e8d5ba805d971660edfd4f51478f2edc3dcb1b9e27c8f60f3f9ae92a1dd619b064b229a083fc3c2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "21587" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 19 Oct 2012 07:42:49 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 27 Sep 2012 18:42:30 GMT" + }, + { + "age": "1315325" + }, + { + "x-amz-cf-id": "T9aSuzcFAaMOSl0BfGK1RZtk2bHJRFveb6whI8ExXPmes7P1gEIr_g==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 267, + "wire": "88ec0f0d840b4f01efca6196c361be940b4a6e2d6a080112817ee342b820298b46ffebc9c86c96dc34fd28012996da9410022502e5c13771b0a98b46ff5585684fb816da7f02ad07117bfcaa2d36630fa0445d86cb38fbe488dbe7bfd305da19e4f591fb739661479c2d7a9f861c3a3de89a083fc7c6408725453d44498f57842507417f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "14808" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 14 Sep 2012 19:42:20 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Sat, 02 Jun 2012 16:25:51 GMT" + }, + { + "age": "4296154" + }, + { + "x-amz-cf-id": "0V2zXn_NrH1y0_eQiJhavI_iThDjEBl3W8rbz6WK2bL14yhUFFMzMg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 268, + "wire": "885f88352398ac74acb37f0f0d03393239d0e0f0cecd6c96dd6d5f4a080a65b6a5040081403f71a72e040a62d1bfdf7f02aeebbb771dd967e5c6d75d91d8ff6c1387f0ea7179853e7664cb9bcfbbbbaad1be35aef747e5dd5f97b8f7a6cd9041cbca0f13a2fe5a0ffb73f38c866e9cf16efc65c8b77365c8af6dfa07d03e9973e99722ffa0ff3f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "929" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 15:34:26 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Sun, 20 Jun 2010 09:46:10 GMT" + }, + { + "age": "163828" + }, + { + "x-amz-cf-id": "kSSVSJhWVu77d7bZr26ow7tGxAtxQIJKxzBSnMTb-BvsXBOXCVvmrQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"41+6XVdi5mL_SX36_SY36_CR,0,0,36,36_#1\"" + } + ] + }, + { + "seqno": 269, + "wire": "886196dc34fd280654d27eea0801128166e01ab8db4a62d1bff358b1a47e561cc5801f4a547588324e5fa52a3ac849ec2fd295d86ee3497e94a6d4256b0bdc741a41a4bf4a216a47e47316007f4085aec1cd48ff86a8eb10649cbf6496df3dbf4a002a651d4a05f740a0017000b800298b46ff4003703370c1acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7e94bdae0fe75ee84ea6bdd7cea6ae1b54dd0e85356fdaa5fddad4bdab6ff30f1fed9d29aee30c2171d23f67a961c88f4849695c87a5835acff92403a47ecf52e43d3f030c1f0314000802565f95d6c637d969c6ca57840138f3856656c51904eb4dc641ca2948db6524b204a32b8d3a171d1bcc9491c6dfc1e892239e007c5500596c2fb4ebce81e71bf89084813f4087aaa21ca4498f57842507417f0f28c11c8b5761bb8c9ea007da97cf48cd540b8e91fb3d4b0e447a424b4ae43d3f6a60f359ac2a20df3dbf4a002b651d4b080cbaa0017000b800a98b46ffb5358d33c0c77b9384842d695b05443c86aa6fae082d8b43316a4f5a839bd9ab0f0d820ba05f95497ca589d34d1f649c7620a98326ed4b3cf36fac1fca", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:54 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\", CP=\"PSAo PSDo OUR SAM OTR DSP COR\"" + }, + { + "location": "http://s.amazon-adsystem.com/iu3?d=amazon.com&a1=&a2=0101e39f75aa93465ee8202686e3f52bc2745bcaf2fc55ecfd1eae647167a83ecbb5&old_oo=0&n=1351947870865&dcc=t" + }, + { + "nncoection": "close" + }, + { + "set-cookie": "ad-privacy=0; Domain=.amazon-adsystem.com; Expires=Thu, 01-Jan-2037 00:00:01 GMT; Path=/" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "170" + }, + { + "content-type": "text/html;charset=ISO-8859-1" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 270, + "wire": "8b5f87497ca589d34d1f0f0d03323436dc4088f2b0e9f6b1a4585fb485eaf5170eff5ef952eb93fe70730cb9faec95a23fe3f6f6a943b30bfb5adf8baf4fcc192ad996b7251d9d7acc0e57418e5f04cd408cf2b0e9f6b585ed6950958d278c6ae819142fb8cbb7dd0bd7dbc9409ff2b0e9f6b52548d6e854a194ac7b0d31aa1d0b4a6a0ab4834956320ef3800f92100215821580f6f0bd7197ae32eae0003f7f408ef2b0e9f6b52548d6a646d69c689f971c71802f3318c6028df7db8da764210057df74407db1ff588aa47e561cc581e71a00016c96df3dbf4a320521b66504008940bd7002b82654c5a37f0f139afe4647c4ccb1b8dc6cb91ba57249431bcd9647c622963137fcff52848fd24a8f76868691fb3d5b9955850b6e81e17f7f12aed9fb949f99713b94cf78b5605f75e7ff63e50d43c7571d2b7e5de1d6daf0f38f1efa71cf8c98eccbd9dd6fec820f7caf0ae0500e46cbd18a49091c6d36478acb3246170231321702d3eb9283db24b61ea4af5152a7f57a83db261b0f527fbfdf", + "headers": [ + { + ":status": "304" + }, + { + "content-type": "text/html" + }, + { + "content-length": "246" + }, + { + "connection": "keep-alive" + }, + { + "x-amz-id-2": "A8pOeFTyzWm76hXU6FfLkQf4c9wZCOf1QF9R4TGkjXEInQJp6farkkg0WB0HfwcK" + }, + { + "x-amz-request-id": "4B032A9637D718D5" + }, + { + "date": "Sat, 03 Nov 2012 13:04:54 GMT" + }, + { + "x-amz-meta-jets3t-original-file-date-iso8601": "2011-11-08T18:38:37.000Z" + }, + { + "x-amz-meta-md5-hash": "abb0183baa0ea995b47dcc0e9972095a" + }, + { + "cache-control": "max-age=864000" + }, + { + "last-modified": "Thu, 30 Aug 2012 18:02:23 GMT" + }, + { + "etag": "\"ac923fb65b36b7e6df1b85ed9a2eeb25\"" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "AmazonS3" + }, + { + "age": "157082" + }, + { + "x-amz-cf-id": "QZJcXJG7Ji8wu-0D789ZbWAnaHnVN-XBUkupFYbHTmHhHcHrJq7P9Q==" + }, + { + "via": "1.0 06b38b2ddcbb45c8e33db161a2316149.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 271, + "wire": "887689bf7b3e65a193777b3f5f911d75d0620d263d4c795ba0fb8d04b0d5a70f0d03343531cdd4", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "451" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:04:54 GMT" + } + ] + }, + { + "seqno": 272, + "wire": "887685dc5b3b96cf6c96c361be94036a6a225410022502ddc106e32ea98b46ffc07b8b84842d695b05443c86aa6fd05892aed8e8313e94a47e561cc581c13c2132dbc26497df3dbf4a32053716b5040644a05bb8cbb71b714c5a37ffd90f0d03383536ee", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 05 Oct 2012 15:21:37 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=628223582" + }, + { + "expires": "Thu, 30 Sep 2032 15:37:56 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:54 GMT" + }, + { + "content-length": "856" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 273, + "wire": "88c26c96d07abe9413ea6a225410022500d5c08ae082a62d1bffc4c1d35893aed8e8313e94a47e561cc581c640cbccb6e3df6496d07abe94136a6a2254101912816ee32edc684a62d1bfdc0f0d8371f75ff1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Mon, 29 Oct 2012 04:12:21 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=630383568" + }, + { + "expires": "Mon, 25 Oct 2032 15:37:42 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:54 GMT" + }, + { + "content-length": "6979" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 274, + "wire": "88c56c96e4593e940baa6a225410022502e5c03d704e298b46ffc7c4d65892aed8e8313e94a47e561cc581c13e270426836496df697e940894d444a820322502e5c03d71b6d4c5a37fdf0f0d830bce03f4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 17 Oct 2012 16:08:26 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=629262241" + }, + { + "expires": "Tue, 12 Oct 2032 16:08:55 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:54 GMT" + }, + { + "content-length": "1860" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 275, + "wire": "88c86c96d07abe940bca681fa504003ea041700edc69a53168dfcac7d9588da47e561cc581b780db2cbccb7f6496d07abe9413aa6e2d6a080c894082e342b8cbaa62d1bfe20f0d827820f7408af2b10649cab5073f5b6b9bd19376e525b0f4a8492a58d48e62a171d23f67a9721e9b81001e07", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Mon, 18 May 2009 10:07:44 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "max-age=580533835" + }, + { + "expires": "Mon, 27 Sep 2032 10:42:37 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:54 GMT" + }, + { + "content-length": "810" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-lookup": "MISS from cdn-images.amazon.com:10080" + } + ] + }, + { + "seqno": 276, + "wire": "88ceda0f0d03353136dce3", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "text/html" + }, + { + "content-length": "516" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:04:54 GMT" + } + ] + }, + { + "seqno": 277, + "wire": "48826402768586b19272ff7f22c7acf4189eac2cb07f33a535dc61848e65c72525a245c87a58f0c918ad9ad7f34d1fcfd297b5c1fcebdd09d4d7baf9d4d5c36a9ba1d0a6adfb54bbc37297f76b521cf9d4bdab6ff30f1fbe9d29aee30c2171d23f67a961c88f4849695c87a58292967fc3490472c827c3201613e369668ac8161b2312cf82394842b6191e97e0be601c9496891721e90f0d033237355f95497ca589d34d1f6a1271d882a60320eb3cf36fac1fe7408721eaa8a4498f5788ea52d6b0e83772ff0f28d3a4b449120a84411cb209f0c80584f8da59a2b20586c8c4b3e08e5210ad8647a5fb2f9acd615106eb6afa500da9a07e941002ca8066e36fdc642a62d1bfeeb1a67818fb90f48cd54091ccb8e4a4b448b90f4fdf", + "headers": [ + { + ":status": "302" + }, + { + "server": "Apache" + }, + { + "p3p": "policyref=\"http://tag.admeld.com/w3c/p3p.xml\", CP=\"PSAo PSDo OUR SAM OTR BUS DSP ALL COR\"" + }, + { + "location": "http://s.amazon-adsystem.com/ecm3?id=bfd291d0-29a4-4e30-a3a2-90bfcce51d8f&ex=admeld.com" + }, + { + "content-length": "275" + }, + { + "content-type": "text/html; charset=iso-8859-1" + }, + { + "date": "Sat, 03 Nov 2012 13:04:54 GMT" + }, + { + "connection": "keep-alive" + }, + { + "set-cookie": "meld_sess=bfd291d0-29a4-4e30-a3a2-90bfcce51d8f;expires=Sun, 05 May 2013 03:59:31 GMT;path=/;domain=tag.admeld.com;" + } + ] + }, + { + "seqno": 278, + "wire": "88eb6c96df3dbf4a09d53716b504008940bd7042b806d4c5a37f6196c361be940094d27eea080112816ae34fdc138a62d1bf6496dc34fd280654d27eea080112816ae34fdc138a62d1bf4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb768344b2970f0d84136cbc0f408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f5584780113df5890aed8e8313e94a47e561cc581e71a003f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Thu, 27 Sep 2012 18:22:05 GMT" + }, + { + "date": "Fri, 02 Nov 2012 14:49:26 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 14:49:26 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "25380" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "80128" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 279, + "wire": "88f0d9efeeedec0f1fed9d29aee30c2171d23f67a961c88f4849695c87a5835acff92403a47ecf52e43d3f030c1f0314000802565f95d6c637d969c6ca57840138f3856656c51904eb4dc641ca2948db6524b204a32b8d3a171d1bcc9491c6dfc1e892239e007c5500596c2fb4ebce81e71bf89084813feb0f28c11c8b5761bb8c9ea007da97cf48cd540b8e91fb3d4b0e447a424b4ae43d3f6a60f359ac2a20df3dbf4a002b651d4b080cbaa0017000b800a98b46ffb5358d33c0c7eae90f0d0235375f87352398ac4c697ff5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:54 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\", CP=\"PSAo PSDo OUR SAM OTR DSP COR\"" + }, + { + "location": "http://s.amazon-adsystem.com/iu3?d=amazon.com&a1=&a2=0101e39f75aa93465ee8202686e3f52bc2745bcaf2fc55ecfd1eae647167a83ecbb5&old_oo=0&n=1351947870865&dcc=t" + }, + { + "nncoection": "close" + }, + { + "set-cookie": "ad-privacy=0; Domain=.amazon-adsystem.com; Expires=Thu, 01-Jan-2037 00:00:01 GMT; Path=/" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "57" + }, + { + "content-type": "image/gif" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 280, + "wire": "88da6c96c361be9413ca6e2d6a080112807ae36ddc0b2a62d1bfdcd9eb5893aed8e8313e94a47e561cc581c13ce32c85a7bf6496df697e94036a6a2254101912807ee09ab801298b46fff40f0d840b8dbcf7ca", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 28 Sep 2012 08:55:13 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=628633148" + }, + { + "expires": "Tue, 05 Oct 2032 09:24:02 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:54 GMT" + }, + { + "content-length": "16588" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 281, + "wire": "88dd6c96e4593e94085486bb1410022502fdc037704d298b46ffdfdcee5892aed8e8313e94a47e561cc581c0ba17c0e3406496df697e94136a681fa5040644a08571b6ee32d298b46ff70f0d84081d685fcd", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 11 Apr 2012 19:05:24 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=617190640" + }, + { + "expires": "Tue, 25 May 2032 22:55:34 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:54 GMT" + }, + { + "content-length": "10742" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 282, + "wire": "88e06c96c361be94136a681fa5040089403571a72e05953168dfe2dff15892aed8e8313e94a47e561cc581c105c105e6856497c361be940b8a65b685040644a01bb8d3d71b754c5a37ff6196dc34fd280654d27eea0801128166e01ab8db6a62d1bf0f0d8365f701d1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 25 May 2012 04:46:13 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=621621842" + }, + { + "expires": "Fri, 16 Jul 2032 05:48:57 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:55 GMT" + }, + { + "content-length": "3960" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 283, + "wire": "88e46c96c361be94138a6a225410022500fdc033704053168dffe6e3f55893aed8e8313e94a47e561cc581c640d3cdbee83f6496df697e94138a6a22541019128205c035704d298b46ff6196dc34fd280654d27eea0801128166e01ab8db4a62d1bf0f0d840b400bffd5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 26 Oct 2012 09:03:20 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=630485970" + }, + { + "expires": "Tue, 26 Oct 2032 20:04:24 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:54 GMT" + }, + { + "content-length": "14019" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 284, + "wire": "886196dc34fd280654d27eea0801128166e01ab8db8a62d1bfe94088f2b0e9f6b1a4583f910b9fba2ebcf716c19b972d9f98767ee9db7f1aff12acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7eaf6b83f9bd0ea52feed6a67879297b86d521bfa14c9c613a9938df3a97b5693a9ab7eb3a9ab86d52fe0ce6535f0ba65356fda652ef0dca6bc7cd4d5a73a9c34e4535f0daa61c9a54bdab429a61e2a64d3bd4bf834297b4ef5376f854c7821535edc0a67d5794c5ab8a9ab7de53f94088f2b0e9f6b1a4585fb306be0ff1d29eefb1cf1a67f36f8bfe316cbfeddeb8f9bd8b3cf64f1ec431082ec79250fa6311af6ad3eb2a534d09c13557c7fb7b9384842d695b05443c86aa6fae082d8b43316a4f5a839bd9abd20f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dff798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:56 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "16ZMB88V50KWWQXFQZNR" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "0PU9VNtv9/YHthxuwDwGQDz7kHY8GLhrhbQs/A0BbIf1y/GiCONyJttmltEgnDaZ" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 285, + "wire": "88c4408cf2b0e9f752d617b5a5424d27990806e94b2bcb09b8dd58212896188a059e8e423c379b8dc75bd4c1c00f0d023533", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:56 GMT" + }, + { + "x-amzn-requestid": "10a7eef8-25b7-11e2-a2e0-8bdc8a85b675" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "53" + } + ] + }, + { + "seqno": 286, + "wire": "88c5f07f05910b99b183885b771037fe7e6ce3bf687f7fc47f04b6731fa745fbb276bfda7c6fedf8f82f9ba7bdb263bf5efeef06acb9fd79c3fba2ec7d34e3eb1bddbf65bfa9df4d7fb75bb726fc3a3a53c3c25f96497ca589d34d1f6a1271d882a60c9bb52cf3cdbeb07f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dffc2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:56 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "16KH0V157G0TXXQVTR1Z" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "6Hy72ZQh4+twTqX90DijzRdHDpTv81nJLyxFZMBbjNHkb8qZfDO7y4+75uITFMjm" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html; charset=ISO-8859-1" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 287, + "wire": "88d70f0d8310822fe06196df3dbf4a09d53716b50400894035700fdc0bca62d1bff45891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96e4593e94032a436cca08010a8205c65db8c894c5a37f5585644279f65e4089f2b0e9f6b12558d27fac2c56966e17b1d5eb102f620f9e59ecd378d77185179eb77f0a2358ac865df7bd88016717e1aeecafbb3341077caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f0f138efe421ff7251297859773ffd07f9f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "2212" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 27 Sep 2012 04:09:18 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 03 Aug 2011 20:37:32 GMT" + }, + { + "age": "3228938" + }, + { + "x-amz-cf-id": "e_uegUCHnyG0CG1xWLrNCiBH1sC8uTUlb-e31fTCz2013GXiBQpv3g==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"11+dlfeUrBL#1\"" + } + ] + }, + { + "seqno": 288, + "wire": "885f88352398ac74acb37f0f0d830b8dbde96196e4593e940b8a681fa504008940bb71a72e34ca98b46f7685dc5b3b96cfc7c60f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96d07abe941054d27eea08010a816ae059b827d4c5a37f55860b4eb6eb4fb37f06acb3a70e2ec782f46cca7fc4acd18014621069a3be898efd5aad770249a0b939fbf597772e5db4e9a2feaf1041c5c4", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1658" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 16 May 2012 17:46:43 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Mon, 21 Nov 2011 14:13:29 GMT" + }, + { + "age": "14757493" + }, + { + "x-amz-cf-id": "rjUV7bECb3foXt-4i01sG21mlvMgo9nOu7EtcMeIYzyJSWWqNNlDOw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 289, + "wire": "88c30f0d840b8cb8d7ee6196e4593e940b4a5f291410021500cdc0bb719794c5a37fc26c96c361be94034a693f75040085413371a7ae34253168dfcccb558613c10b6265ef7f02acc8e7201f69b426ddb7ef5ffd2a72d8f5f4eaf3602ad13bdb2bcc27e924d8045d18b07871f3cda07ae49a083fc9c80f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "16364" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 14 Dec 2011 03:17:38 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Fri, 04 Nov 2011 23:48:42 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "28115238" + }, + { + "x-amz-cf-id": "I6W0oRiMtuRDCDZetJr8DtOxr0nMh8QpK29mcgE2eMGEw69ogMaPdg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 290, + "wire": "88c56c96e4593e940b2a65b68504008540bb702f5c6c0a62d1bf5f911d75d0620d263d4c795ba0fb8d04b0d5a77b8b84842d695b05443c86aa6fd85892aed8e8313e94a47e561cc581c0b8013ee05c6496e4593e940894d03f4a080c89408ae09bb811298b46ffdf0f0d03353934f7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 13 Jul 2011 17:18:50 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=616029616" + }, + { + "expires": "Wed, 12 May 2032 12:25:12 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:04:56 GMT" + }, + { + "content-length": "594" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 291, + "wire": "88dfca7f189107e33a78edd70eec18b6f2e37b1e30e367de7f18b5ff724dbcb46d6f5f7e8dc35eaaedaff5b23583f3d62bb572456c5e4ef3cf77b72fcb335a6dd92eefff7fbbb830f8b1dfc790f77ceedddcd70f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dffdb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:56 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "0X3NVRPASEGRWVCHH1H3" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "+dgTelR5Pvj5ApOpupZ5c4EXyGBnWsp/CtTohBqWXrKuiSIBT+ZSU/92HDHIoBxS" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html; charset=ISO-8859-1" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 292, + "wire": "88ce0f0d8313e017408721eaa8a4498f5788ea52d6b0e83772ff6196dc34fd282754d444a820044a099b8cb5702253168dffced7d66c96c361be941014cb6d0a080112816ee36fdc680a62d1bf55856dc740d35f7f0aae8f9e7e44f7c9a9e23f6db9ff377afd9341d9ebd3723b70fdd3c8f8bf2ce39e6ee3c9c7b687b77e5940f59890c107d5d4", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2902" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 27 Oct 2012 23:34:12 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 20 Jul 2012 15:59:40 GMT" + }, + { + "age": "567044" + }, + { + "x-amz-cf-id": "bYLWczW4h_oqRLXSyZdMo3kjSsqUZNWoGXrVLgvaIVqM8SXrlaPicA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 293, + "wire": "88d30f0d83642db7c26196df3dbf4a09c532db42820044a01fb8172e32ca98b46fd2dbda6c96df3dbf4a05f532db42820044a05db8105c0894c5a37f558679c6d9740cff7f02acafce1ba5e9ed0d1f6cd4f6f3cc824394c7ba2d977ad468e9f5af3ecc523d83a7759e272a00d63d98b5a61820d9d80f13a2fe5a0ffb73f38c866e9cf16efc65c8b77365c8af6dfa07d03e9973e99722ffa0ff3f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3155" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 26 Jul 2012 09:16:33 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 19 Jul 2012 17:10:12 GMT" + }, + { + "age": "8653703" + }, + { + "x-amz-cf-id": "pxFBejzs4oRgmqxYc2s6mbS_QBknibmyPLQGd8Ejv-8cWl04HQGPtA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"41+6XVdi5mL_SX36_SY36_CR,0,0,36,36_#1\"" + } + ] + }, + { + "seqno": 294, + "wire": "88d70f0d8365b65cc66197dd6d5f4a09d5340fd2820044a01eb8d3571b754c5a37ffd6dfde6c96df3dbf4a05f5340fd2820042a05ab8d39704f298b46f55860b2f32fb6fbf7f02aeefbcfe7ac9c69ddfbb37038b26dbfba169af1063909ef467e7fbe22db830f3c5beef0a96c7adbc72c9096fc3041fdddc", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3536" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 27 May 2012 08:44:57 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 19 May 2011 14:46:28 GMT" + }, + { + "age": "13839599" + }, + { + "x-amz-cf-id": "vToxkdVmSZQS0V3iRZM-gCcaadczMLYZw_REFYGTBUn-HP5HfdAeDA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 295, + "wire": "88db0f0d83642d33ca6196d07abe94038a436cca080112816ee36cdc65f53168dfdae3e26c96d07abe94038a436cca0801128066e32f5c0094c5a37f558675c75f69d77f7f02ac75b1789af8f0947d9ac9b3a1898cbdbd70367e793a60e8820d6abb9059ee88ed74d5f9a37befe87300a6820fe1e0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3143" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 06 Aug 2012 15:53:39 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 06 Aug 2012 03:38:02 GMT" + }, + { + "age": "7679477" + }, + { + "x-amz-cf-id": "752wgDaFeaq4IQjicHeqyUiLYIjEjsca-nvc2LB2o4jOXMT99M6E2g==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 296, + "wire": "88df0f0d8364407fce6196df697e9413ca612c6a0801128215c6d9b820a98b46ffdee7e66c96df3dbf4a082a6a225410020502fdc6c571a0298b46ff5586105a75e13edf7f02ade17e6d4f399e677ba23db6458f3fb047be2c4b4b6bb7b6c3d7adda6fc5a70c84a31e0f7e437b2d7b674e6f1041e5e40f1397fe590ad61900e1a079e2dd9db0022ddb820045ff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3209" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 28 Feb 2012 22:53:21 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 21 Oct 2010 19:52:40 GMT" + }, + { + "age": "21478295" + }, + { + "x-amz-cf-id": "UDgO86Lg7vsbRr_HLz0bT_G-fu7CRAkkBmD_NFdclHEzx1CJpRhtKw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"31-ris0UMaL_SL500_SS100_#1\"" + } + ] + }, + { + "seqno": 297, + "wire": "88e30f0d83644107d26196dc34fd282754d444a820044a099b8d37700153168dffe2ebea0f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96c361be941014cb6d0a080112816ee36fdc69953168df55856dc7197dbf7f02adb3070aeb188208fd65f5bf22778f6ec158b749fc70f17da20af66d5b7d8a1c59479e7e97bf62cfb05e1ff1041fe9e8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3210" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 27 Oct 2012 23:45:01 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Fri, 20 Jul 2012 15:59:43 GMT" + }, + { + "age": "566395" + }, + { + "x-amz-cf-id": "rEUppa210byJyTItTaRQ2r-jhwUwD4c2CKORz2AGJaLhjCZ_LQ2w9w==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 298, + "wire": "886196dc34fd280654d27eea0801128166e01ab8db8a62d1bfe67f1a91060dd95f0ded0e6d79fdf8b96367665c5f4003703370ff12acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7eaf6b83f9bd0ea52feed6a67879297b86d521bfa14c9c613a9938df3a97b5693a9ab7eb3a9ab86d52fe0ce6535f0ba65356fda652ef0dca6bc7cd4d5a73a9c34e4535f0daa61c9a54bdab429a61e2a64d3bd4bf834297b4ef5376f854c7821535edc0a67d5794c5ab8a9ab7de53f97f1bb6d137fbf1cf659e928bb7eac78f37afda3169b3aed6f17be3a3f3bf5c96e5ebb5e707cd147bfdd6ddecbf8c60c1a9b85e207b8b2f56bf7b9384842d695b05443c86aa6fae082d8b43316a4f5a839bd9ab5f87352398ac4c697f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dff798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:56 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "0ESJ91CM6R89TGWH3QJG" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "Mg+wYQrytsBDnHHKyZlGNrkR5GzVMXvkIuJkR86aYslzZP5CJX/EEO5A8c1v2Jk4" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 299, + "wire": "88bf0f0d841044f0bfde6196df3dbf4a002a693f750400894106e01fb8d32a62d1bfee5891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96df3dbf4a002a693f750400894106e01fb8c854c5a37f55850b4cba16bf7f0caee987b79d35eacd91cc7cb5f6de866c1e7c9b66ab292d6716d2e1db9d9d1d0d1f04bf0ebf0eed7ef4bef4f6d9041f7caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f0f138efe421ff7251297859773ffd07f9f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "21282" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 01 Nov 2012 21:09:43 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 21:09:31 GMT" + }, + { + "age": "143714" + }, + { + "x-amz-cf-id": "jFqxNpOKI6HWPqTs3raLIRgnJcu3GReFRL3MjibUt9APw7R9CfzNqQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"11+dlfeUrBL#1\"" + } + ] + }, + { + "seqno": 300, + "wire": "88c70f0d03343339e66196dc34fd282029a889504008940bf702f5c136a62d1bfff6c5c46c96df3dbf4a019532db52820040a05cb8172e05d53168df5585085e742fb37f04ad6cb53ddd3a8003efd3b7cb7d11eb2f3f3e2dd7a3071b659997b2ebc9d5db7d263fd361d2ebbdf826e9a39a083fc3c20f138efe421ff7251297859773ffd07f9f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "439" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 20 Oct 2012 19:18:25 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:16:17 GMT" + }, + { + "age": "1187193" + }, + { + "x-amz-cf-id": "5en8vtO00oTNRx5jsyJYxwuPMEVufg38JPIk7uytbZiFN77vUtBibg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"11+dlfeUrBL#1\"" + } + ] + }, + { + "seqno": 301, + "wire": "886196dc34fd280654d27eea0801128166e01ab8dbaa62d1bf7685dc5b3b96cf7f13900c375ad78c50be1d98d9e79728b95efdd27f12b4e2df8fc03eff5dcf37f85e3d5a72bfd9bbe3749d26cde43c87e2adf97af6e699dbf5d4d54ad16973f7b4da68fdeafe6628d71fafd1d05f96497ca589d34d1f6a1271d882a60c9bb52cf3cdbeb07f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dffcf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:57 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "1AB4PH2A91QH3YJJ2WCZ" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "V5wX099kS85XeVk46pZgvH7cjgKx1WawnTJkqYth5ykinf4em6ZqgNlZk9K/lPby" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html; charset=ISO-8859-1" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 302, + "wire": "88d00f0d023637ef6196d07abe941094d444a820044a01bb8d82e09d53168dffc2cecd6c96df3dbf4a019532db52820040a05cb8172e05a53168df5585081c13ce877f07ad2e4afacddb6e2ebe87bf935af767413bd83479b6fa34332b5a7259af9f7738e40a3f5316498de7566d3143041fcccb0f138efe421ff7251297859773ffd07f9f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "67" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 22 Oct 2012 05:50:27 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:16:14 GMT" + }, + { + "age": "1062871" + }, + { + "x-amz-cf-id": "eIpkgqRGkyaTW4PSLscvrasxuDsM3f4NIrPYv6VI1sZt_IgixOKN_A==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"11+dlfeUrBL#1\"" + } + ] + }, + { + "seqno": 303, + "wire": "88d40f0d03383631f36196c361be940bea6a225410022500edc139704fa98b46ffc66c96dc34fd282694d27eea0800754033700d5c0054c5a37fd3d255850b2171903f7f02ad6f45df19fbe5c0ce06fbcc0eb3f3c155bcbf1cdb8bdded49f52e1a79f3f42aadf69c83f59dfd40ac7800786083d0cf0f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "861" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 19 Oct 2012 07:26:29 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Sat, 24 Nov 2007 03:04:01 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "1316309" + }, + { + "x-amz-cf-id": "5MBwLvJE3E5vg0khYEnuWX6RGzCOtyfFmYYy2nuztIayL9O0paE0oA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 304, + "wire": "88d80f0d023433408721eaa8a4498f5788ea52d6b0e83772ff6196df697e94132a6a225410022500fdc08ae32ea98b46ffcbd7d66c96c361be94034a65b6a50400814006e34d5c0baa62d1bf55857dc699683f7f03ade4de0d31a496736abe3999b1dd37df6b2e5c7f84c1d3a5d2135bbb1d77deadc6fdd9c26e49d776a75ba31f8820d5d4", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 23 Oct 2012 09:12:37 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 04 Jun 2010 01:44:17 GMT" + }, + { + "age": "964341" + }, + { + "x-amz-cf-id": "W5ENbtcrY4pVK3r7ND94JJHXcEjjBccP7Q77zOSiZQUgWtPBn75lHw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 305, + "wire": "88dd0f0d830b6fbdc26196dc34fd282029a889504008940b771b0dc03aa62d1bffcfdbda6c96df3dbf4a019532db52820040a05cb8172e09f53168df5585085f7dc6437f02aeab0f3df0dd86f1ead13bdbe297f31c3a713f158ce1e9eb84b56ebbcba792c62c5db2e64cc68a9d3b727e68f1041fd9d8408725453d44498f57842507417f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1598" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 20 Oct 2012 15:51:07 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:16:29 GMT" + }, + { + "age": "1199631" + }, + { + "x-amz-cf-id": "nFYTABAConMh8T_fXHANG9_r3FjyUfnSBWjxeb2GqJKtgi_mNRIXMw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 306, + "wire": "88e20f0d830b8dbbc76196df697e94136a6e2d6a0801128176e09eb8d814c5a37fd4e0df6c96df3dbf4a019532db52820040a05cb8172e32053168df55866596d975c7bf7f03ad42324bf9f97e3bb5bd66b882d08bc38ea8cfa7873fc6b21cf237b71c5b0aff277c6b0f98fafed6fe8bd3f8820fdedd", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1657" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 25 Sep 2012 17:28:50 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:16:30 GMT" + }, + { + "age": "3353768" + }, + { + "x-amz-cf-id": "ssIfXXDbBp8rP_142eUVOboNUYX4Iood5RH_Qe9W7wP1xbkZp9MChw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 307, + "wire": "885f88352398ac74acb37f0f0d83640167cc6196d07abe9413ca693f75040085410ae09ab806d4c5a37fd9e5e40f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96d07abe941054d27eea08010a816ae059b827d4c5a37f558613ed09e79b677f03adefa81b40fcffb79cdf57a9f469dfc5fe980b3462bf369a71adfa1cc3d86f45dd1a39d2f14f7538f8557fc4107fe3e2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3013" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 28 Nov 2011 22:24:05 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Mon, 21 Nov 2011 14:13:29 GMT" + }, + { + "age": "29428853" + }, + { + "x-amz-cf-id": "vO0R09hZC6TnyhMNTV9jEegb2DgNmH-Z1KaQiyeSbsYm8eoBtHUnDw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 308, + "wire": "887689bf7b3e65a193777b3f5f911d75d0620d263d4c795ba0fb8d04b0d5a70f0d03343535ee6196dc34fd280654d27eea0801128166e01ab8dbca62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "455" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:04:58 GMT" + } + ] + }, + { + "seqno": 309, + "wire": "88c05f87497ca589d34d1f0f0d83136217f0bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "text/html" + }, + { + "content-length": "2522" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:04:58 GMT" + } + ] + }, + { + "seqno": 310, + "wire": "88bf768586b19272ff4083928891831840d74085f2b4e7427f881840d297e00be2177f37e6bdae0fe61cf9d4bfbb5a97b56d535ee846a6bdd7c6a6ae1b54c9a6fa97b568534c3c54c9a77a99f55e5356fbdfcfd2959e8313d585960fe674a6bb8c3049d7ed6950931eaa476752a5721e963c3246076c8648800758ad9ae2bfeaa1d262673cc622fe69a3f97b8b84842d695b05443c86aa6f0f0d033535384088ea52d6b0e83772ff8f49a929ed4c01103e94a47e607d96bf7f1b88cc52d6b4341bb97f5f92497ca589d34d1f6a1271d882a60b532acf7f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:58 GMT" + }, + { + "server": "Apache" + }, + { + "dl_s": "a104" + }, + { + "x-host": "a104 D=1922" + }, + { + "p3p": "CP=\"ALL DSP COR PSAa PSDa OUR IND COM NAV INT LOC OTC\", policyref=\"http://ch.questionmarket.com/w3c/audit2007/p3p_DynamicLogic.xml\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "558" + }, + { + "keep-alive": "timeout=120, max=934" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "text/html; charset=utf-8" + } + ] + }, + { + "seqno": 311, + "wire": "88c7c5c47f04871840ca97e01059c3c20f0d0237387f028f49a929ed4c01103e94a47e607da0ffc1c06496d07abe94138a65b68502fbeea806ee001700053168df5892ace84ac49ca4eb003e94aec2ac49ca4eb0034085aec1cd48ff86a8eb10649cbf0f28c7d79e089f75a7402582b3ccb2269a103ed42f9acd615106eb6afa500cada4fdd61002ca8166e01ab8dbca62d1bfed4ac699e063ed490f48cd540bf6b4a8498f5523b3a952b90f4f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:58 GMT" + }, + { + "server": "Apache" + }, + { + "dl_s": "a104" + }, + { + "x-host": "a103 D=213" + }, + { + "p3p": "CP=\"ALL DSP COR PSAa PSDa OUR IND COM NAV INT LOC OTC\", policyref=\"http://ch.questionmarket.com/w3c/audit2007/p3p_DynamicLogic.xml\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "78" + }, + { + "keep-alive": "timeout=120, max=941" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "expires": "Mon, 26 Jul 1997 05:00:00 GMT" + }, + { + "cache-control": "post-check=0, pre-check=0" + }, + { + "pragma": "no-cache" + }, + { + "set-cookie": "PL=_974702-1-83324420; expires=Sun, 03-Nov-2013 13:04:58 GMT; path=/; domain=.questionmarket.com" + } + ] + }, + { + "seqno": 312, + "wire": "88d36c96e4593e940baa6a225410022502edc13f702d298b46ff6196dc34fd280654d27eea080112806ee32d5c0b6a62d1bf6496dd6d5f4a01a5349fba820044a01bb8cb5702da98b46f4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb768344b2970f0d8413aeb2ef408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f558413a0699f5890aed8e8313e94a47e561cc581e71a003f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Wed, 17 Oct 2012 17:29:14 GMT" + }, + { + "date": "Sat, 03 Nov 2012 05:34:15 GMT" + }, + { + "expires": "Sun, 04 Nov 2012 05:34:15 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "27737" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "27043" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 313, + "wire": "88d4d27f128318822f7f0c871882252fc0e85bd1d00f0d0234337f0c8f49a929ed4c01103e94a47e607dd67fcf5f87352398ac4c697fcccbca0f28c5c1ba07dd69d00960c8df6d2b03ed42f9acd61510722c9f4a09b5af948b0801654037700d5c6de53168dff6a5634cf031f6a487a466aa05fb5a5424c7aa91d9d4a95c87a7ef", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:58 GMT" + }, + { + "server": "Apache" + }, + { + "dl_s": "a212" + }, + { + "x-host": "a212 D=715" + }, + { + "p3p": "CP=\"ALL DSP COR PSAa PSDa OUR IND COM NAV INT LOC OTC\", policyref=\"http://ch.questionmarket.com/w3c/audit2007/p3p_DynamicLogic.xml\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "43" + }, + { + "keep-alive": "timeout=120, max=973" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "image/gif" + }, + { + "expires": "Mon, 26 Jul 1997 05:00:00 GMT" + }, + { + "cache-control": "post-check=0, pre-check=0" + }, + { + "pragma": "no-cache" + }, + { + "set-cookie": "ES=974702-1d5qN-0; expires=Wed, 25-Dec-2013 05:04:58 GMT; path=/; domain=.questionmarket.com;" + } + ] + }, + { + "seqno": 314, + "wire": "88d8408cf2b0e9f752d617b5a5424d279a08998d979e7d61371bab042512cf0e5716186369a95f746c8cbfbf7b9384842d695b05443c86aa6fae082d8b43316a4f5a839bd9ab0f0d023533", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:58 GMT" + }, + { + "x-amzn-requestid": "123b3889-25b7-11e2-8af6-a1b44f97a3ae" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "53" + } + ] + }, + { + "seqno": 315, + "wire": "88dbfc7f3c910feeb7f0b58317e6bc41e41d7cef69cb9b7f18ff12acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7eaf6b83f9bd0ea52feed6a67879297b86d521bfa14c9c613a9938df3a97b5693a9ab7eb3a9ab86d52fe0ce6535f0ba65356fda652ef0dca6bc7cd4d5a73a9c34e4535f0daa61c9a54bdab429a61e2a64d3bd4bf834297b4ef5376f854c7821535edc0a67d5794c5ab8a9ab7de53f94088f2b0e9f6b1a4585fb38519c05e5a30b3fed9f1b897bd6d0478b6b2193767421fa80d9d9b706db38f872bda5c0fdf9b1e50f4cd87850f977cfbfa6dddc2c1c40f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dff798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:58 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "1ZP9F4EGXPG1W1PYCNJK" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "AsL0eWMF3+3wScCyR0bGR31dSLss9n05o3uERrVw6pReE9DgHJ1jKFUl9eThTjRS" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 316, + "wire": "88df7685dc5b3b96cf7f03910b8f5de4d5dc7b3183f7f343b87f346bbbc27f02b5260ce11697d9ff74b5c8f83b614ebc92dec3c0dfed1eac9f33469f9998efb36e0c7daeed41269d9699dbd18f7fefd77bbbb8301bdfc6c5c80f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dffc1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:58 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "168BW4BHQH0ZXM7FXMPB" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "cEL12N93+m4WoEqFtPIfCFUi+syrhK4ihYi/vQREHqBRscgh343Rj/z+yvBSU/1C" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 317, + "wire": "88e2c07f00910ebc21dbbb08736bfdc3bde8713db31b3fc47f00b5bba30617e87cd24dc9dcae676fac3c6f533670e70f1fc67569bf939b44636bbfed8f03bfc33ca9cf9a5c3aa7e924adc7f8fe59bc7fc8c75f96497ca589d34d1f6a1271d882a60c9bb52cf3cdbeb07f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dffc4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:04:58 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "1PF1RSF1KPZFT8AG8QH3" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "BMEF9l9idgW7J6L5kAVCmgL1L1VX3ONDIY4c/R7+/waDULftLKfFOhjdf5bX9Jgw" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html; charset=ISO-8859-1" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 318, + "wire": "88cb0f0d0237367f1e88ea52d6b0e83772ff6196df697e94136a6e2d6a0801128166e321b8d32a62d1bfc55891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96df3dbf4a019532db52820040a05bb8db371b0298b46f558665971d7df73f7f2fad292cf3abc7efacf9399f56476a0e3fd93a43560f556a8deb2addd257cfa2ef7b5fa16a4932d7fba3636986083f7caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f0f138efe421ff7251297859773ffd07f9f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "76" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 25 Sep 2012 13:31:43 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 15:53:50 GMT" + }, + { + "age": "3367996" + }, + { + "x-amz-cf-id": "ecrxOwZyLIYoOI7n1HZdjAnEynOb8rnSjf9oMBvu9l-mcg-DvsQ5tA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"11+dlfeUrBL#1\"" + } + ] + }, + { + "seqno": 319, + "wire": "88f50f0d8313edb3c66196dd6d5f4a05a535112a0801128205c0b371b7d4c5a37fcdc5c40f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96c361be940bca681d8a08010a8266e360b82714c5a37f55850ba0109c077f04addbb6aea1249eb197a63f0fd35d967264b8e7e396fa744d784b0bf62d5efa877a65f58f647a315fdba0336c820fc3c2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2953" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 14 Oct 2012 20:13:59 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Fri, 18 Mar 2011 23:50:26 GMT" + }, + { + "age": "1702260" + }, + { + "x-amz-cf-id": "RRnk1cdyHejHw9mprrW3eHhVJDtMgC2-2Z_Ozk1TtfyHQbMGDRM1gQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 320, + "wire": "88f90f0d8313e07bca6196dc34fd280654d27eea0801128005c13f700153168dffd1c9c86c96d07abe941054d27eea08010a816ae059b8d3aa62d1bf558469b65b7b7f02ae938f1ed5f91f0ef34fb895b79d9e2f75ab4e377d5f059aff1a945b3270ed8f662767e535ff027f28afe2f5b2083fc7c60f13a2fe5a0ffb73f38c866e9cf16efc65c8b77365c8af6dfa07d03e9973e99722ffa0ff3f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2908" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 03 Nov 2012 00:29:01 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 21 Nov 2011 14:13:47 GMT" + }, + { + "age": "45358" + }, + { + "x-amz-cf-id": "dVVqpxaUvghScp5L3V8knNH7yD0rPX4f2QIUqHQG7hWgDw29J2DGyQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"41+6XVdi5mL_SX36_SY36_CR,0,0,36,36_#1\"" + } + ] + }, + { + "seqno": 321, + "wire": "885f88352398ac74acb37f0f0d83138f35cf6196d07abe9413ea6a225410022500fdc6c1700ca98b46ffd6cecd6c96df697e940094c258d410020504cdc13971b0298b46ff558569a65c7dcf7f03aed745f0c5e7a71b4eb73bdedd8bf46d78f5adccf878d47e993c012ff9e9f98eefac777b6117e97b646a38bb4d041fcccb0f1397fe590ad61900e1a079e2dd9db0022ddb820045ff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2684" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 29 Oct 2012 09:50:03 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 02 Feb 2010 23:26:50 GMT" + }, + { + "age": "443696" + }, + { + "x-amz-cf-id": "PlD1_xjVuo-YCz7_Za4wyP6LFVnojIw0t9xjXHByHBqF2ZeqI4b_qg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"31-ris0UMaL_SL500_SS100_#1\"" + } + ] + }, + { + "seqno": 322, + "wire": "88e10f0d03383739d36196dd6d5f4a01e532db42820044a0057196ee32d298b46fdad2d16c96df3dbf4a019532db52820040a05cb8115c69e53168df55860804c89f71bf7f02add3866e51cfae1c9af73908e2d508747e99adb978b2f609c597da4d6ead3934fe7bd0304789c150573a7a86083fd0cf0f138efe421ff7251297859773ffd07f9f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "879" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 08 Jul 2012 02:35:34 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:12:48 GMT" + }, + { + "age": "10232965" + }, + { + "x-amz-cf-id": "NFgWbhPAIPS6Aa_OA1MZi4RJV38Eh2JztiuONINXzMa0bG62le6jyA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"11+dlfeUrBL#1\"" + } + ] + }, + { + "seqno": 323, + "wire": "88e50f0d03383239d76196df697e9413ea693f7504008540b771a72e32d298b46fde6c96df3dbf4a019532db52820040a05cb8115c69f53168dfd7d6558613ecb8e3206f7f02ad03fab1121006f2707115fe5e70e88f368934d37dce5639ef87bfc99ccf475e29bd89e6b981d937e7ddcc1b2083d4d30f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "829" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 29 Nov 2011 15:46:34 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:12:49 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "29366305" + }, + { + "x-amz-cf-id": "09OGcA01CtEV2DWxFMbKMdNmD6Wr6zUzXg6LlkVtCG84Y07dTLSY0Q==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 324, + "wire": "88e90f0d03383734db6196dc34fd282029a889504008940bb71a0dc69c53168dffe2dad9c55585085f13efb37f01aeb3c774f9b3844f7ad0d77c08a99cfe7238f6bd05bdbf4c23c82af25f8a7efaf50ff27cb0f0fcaf393876d5b2083fd7d6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "874" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 20 Oct 2012 17:41:46 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:12:48 GMT" + }, + { + "age": "1192993" + }, + { + "x-amz-cf-id": "rwvtxrU_8yM4vEsn3LxI68PMeCTNAaI2pID_hvPOaXhJAUXpLcUqOQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 325, + "wire": "88ec0f0d03383339de6196dc34fd282794cb6d0a0801128172e36cdc6db53168dfe5dddcc8558679a6d969c6bf7f01ade99a2e2cef7a6bb3d5c7f1c1e2acdef495e6b2019f7b87d31eadf8af003a2ac22d2b7ee6e1877db6fbaa59a083dad9408725453d44498f57842507417f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "839" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 28 Jul 2012 16:53:55 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:12:48 GMT" + }, + { + "age": "8453464" + }, + { + "x-amz-cf-id": "jK_V3T8gBhnVX6aGpizNe84I03zSajHOTGC01MnF2N-ZKUFTuuznfg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 326, + "wire": "88f00f0d03383733e26196df697e940094d444a820044a041700edc6c0a62d1bffe9cce1e0558513aeb6d09f7f02aef7adeb6fcee9f935db1754e1aef65d9466f5eeff7b47c28f0f6e9766bfd2f116acf0e18b09e8f1bd0eebe70c107fdedd", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "873" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 02 Oct 2012 10:07:50 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:12:48 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "2775429" + }, + { + "x-amz-cf-id": "zP8uDh7oW4qGktFpCJQlKyzDvuaUlw8SfQPZeV2OLAF_FolwTs7PYA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 327, + "wire": "88f30f0d03383339e56196df3dbf4a01a535112a0801128066e32f5c0b4a62d1bfece4e3cf5585138270006f7f01acf77b86f775b52490a2189bc1aa4fcdcb149cb7199bc39438afbfe24f0cbb47b2e6976741747e1a71f036c820e1e0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "839" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 04 Oct 2012 03:38:14 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:12:48 GMT" + }, + { + "age": "2626005" + }, + { + "x-amz-cf-id": "zCUT7P4ddAsA_5EOdXS-ecWSi3Caf1GD9wdw37lzeKfQj2j9AmHUiQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 328, + "wire": "88f60f0d03383239e86196dc34fd281794c258d4100225000b8cb771b754c5a37fefe7e66c96df3dbf4a019532db52820040a05cb8115c69c53168df5586109a109b685f7f02aed99b7de2cd7f0f6e3633f979ec792d316f46b63a33e1ad1287be5cbeafe69f7d4fdfc31e5367e5c7f3674cfb2083e5e40f138efe421ff7251297859773ffd07f9f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "829" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 18 Feb 2012 00:35:57 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:12:46 GMT" + }, + { + "age": "22422542" + }, + { + "x-amz-cf-id": "QKTCegDFqVr3XC8HIuieCb-HlLFpsf1vJJyDKhTn9DFbJiLWVXQjLQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"11+dlfeUrBL#1\"" + } + ] + }, + { + "seqno": 329, + "wire": "885f87352398ac4c697f0f0d03383734ed6196dd6d5f4a09f532db42820044a00171966e32f298b46ff46c96df3dbf4a019532db52820040a05cb8115c69d53168dfedec558579a136f3c17f03adc3b2947edbe3a53b649bc70d0de3f3bfc89d4f316313bb35591cc1bf964d3ee2db643a86fbe13b79fbfaf1041feae90f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "874" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sun, 29 Jul 2012 00:33:38 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:12:47 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "8425881" + }, + { + "x-amz-cf-id": "FQmsZuwjmRdgwUM5HxTx27tY2H27QOrbg1DJdNz_RrAOa991o5Lvyw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 330, + "wire": "886196dc34fd280654d27eea0801128166e01bb800a98b46fff87f36910e6c2e5ecb841fb0b3bdf068bd8b03df9f4003703370ff12acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7eaf6b83f9bd0ea52feed6a67879297b86d521bfa14c9c613a9938df3a97b5693a9ab7eb3a9ab86d52fe0ce6535f0ba65356fda652ef0dca6bc7cd4d5a73a9c34e4535f0daa61c9a54bdab429a61e2a64d3bd4bf834297b4ef5376f854c7821535edc0a67d5794c5ab8a9ab7de53f97f37b5c13dd2f5fce28c9c89edcc49c3c5f638f2cb8f47bd87e3fbc2db3fddecc0c1883243828cbd3b6fe55e2ab2db9b4a1e698933fe83b77b9384842d695b05443c86aa6fae082d8b43316a4f5a839bd9ab5f96497ca589d34d1f6a1271d882a60c9bb52cf3cdbeb07f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dff798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:05:01 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "1KF6CJF0ZA3T90MCGE8X" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "EhBekXVsIWcz6GtFV9/VWJHMzQoVZUur+CK0EG1dAElJjqTWpGnJuKNs84/dLZ0q" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html; charset=ISO-8859-1" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 331, + "wire": "88ca0f0d03333934408721eaa8a4498f5788ea52d6b0e83772ff6196df697e94132a6a225410022502e5c69cb80794c5a37f7685dc5b3b96cf5891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f6c96df3dbf4a019532db52820040a00371a72e36d298b46f55857d9742cb5f7f0eadcef9cd3e7eb3de462e1470599e9db0e7f1cbf19dfeadbbb3392b8898c93c774d7a94f085d65cfc7b3573cd041f7caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f0f138efe421ff7251297859773ffd07f9f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "394" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 23 Oct 2012 16:46:08 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 01:46:54 GMT" + }, + { + "age": "937134" + }, + { + "x-amz-cf-id": "L9oihLkhCsGUlU-3jqFLwWX3TyuBQLcp_cHchbBiCmtUA736X8Kphg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"11+dlfeUrBL#1\"" + } + ] + }, + { + "seqno": 332, + "wire": "88d40f0d03363237c76196df3dbf4a042a6a225410022502d5c659b820a98b46ffc6c5c46c96df3dbf4a019532db52820040a05cb816ee34d298b46f55850bef05f0077f04ad769a1aa30b8e76d9bfde4fcc382f7264e4fd6eac4ff7c78e5b79dfa7f2b90b77a67e3b87b361e8bde90f8a1820c3c2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "627" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 11 Oct 2012 14:33:21 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:15:44 GMT" + }, + { + "age": "1981901" + }, + { + "x-amz-cf-id": "7ml4lF66qQTzIXFECW3ocZ5nG9vHHfuYDmXpdeBjLVSaQQolCys92A==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 333, + "wire": "88d80f0d827041cb6196e4593e940814d444a820044a0437041b826d4c5a37ffca6c96df3dbf4a019532db52820040a05cb816ee34e298b46fcac95585101d7de0bb7f02aebc7ce4a777e61122dc39dcd976ad5d3a67f38384f2ba2e39a8b2ba3d5e6b6f3cb44ba76e095c5bf0c0aff3c4107fc7c60f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "621" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 10 Oct 2012 11:21:25 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:15:46 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "2079817" + }, + { + "x-amz-cf-id": "CoLcmSXF2suFL6QBnOjjLxEUhf72VKlrplyC4RYJlfNREf6-Xi0pXw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 334, + "wire": "885f88352398ac74acb37f0f0d83132dbdd06196df697e9413ea693f750400854006e362b8cbca62d1bfcfcecd0f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96d07abe941054d27eea08010a816ae059b827d4c5a37f558613ed05c65a6b7f03ad93699ee68e379bb32e869ea3c0eb6744e073663e363fe1cd5d81ae59990ecdbb731afd01d9bb26f67dc934107fcccb", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2358" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 29 Nov 2011 01:52:38 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Mon, 21 Nov 2011 14:13:29 GMT" + }, + { + "age": "29416344" + }, + { + "x-amz-cf-id": "dRi8YsVC5rJM48lwap3Mh06QHVr9w6Oq0Pfg31QRRKiDl1QSIT3zdg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 335, + "wire": "88e10f0d830b6fb5d46196dc34fd282029a88950400894082e05ab817d4c5a37ffd3d2d16c96df3dbf4a019532db52820040a05cb816ee36fa98b46f55850882fbcd337f02ad0c66b6d7f83eed68a39f1e70d3a30cf678dde355de7c7c7e1b3cde290e8db9b23b14eed02f74f3fdfdc03d9041d0cff0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1594" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 20 Oct 2012 10:14:19 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:15:59 GMT" + }, + { + "age": "1219843" + }, + { + "x-amz-cf-id": "1biuu9U97pslYVYAmMFhrwSwOBYVwXiLgwm1MRKI7_h7l2zmYZZEaQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 336, + "wire": "88e50f0d023433d86196e4593e94138a6e2d6a0801128215c0bd719754c5a37fd7d6d56c96df697e94136a6e2d6a0801128205c139704153168dff5586644d3efbcdff7f02ae97f190bbd6bd5874c8dfeefe37db93868b29dd80bcf6fbed4b5cabf57edcda7109ef595ad4f961efdc7ef878820fd4d30f13a2fe5a0ffb73f38c866e9cf16efc65c8b77365c8af6dfa07d03e9973e99722ffa0ff3f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 26 Sep 2012 22:18:37 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 25 Sep 2012 20:26:21 GMT" + }, + { + "age": "3249985" + }, + { + "x-amz-cf-id": "fX317kpOFNd5ZTVD5dUMrmSEeYRzqm4WpyDuKNG28yJ4O9eAvvazUw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"41+6XVdi5mL_SX36_SY36_CR,0,0,36,36_#1\"" + } + ] + }, + { + "seqno": 337, + "wire": "88e90f0d83081c0fdc6196c361be940bea6a2254100225021b806ae05f53168dffdbdad96c96df3dbf4a019532db52820040a05cb817ae01b53168df55850b20644d337f02aea614fe76eb0f5bcd5fe87ae7bbef9e0e4dee7365767e379bd5ffdaf5fbf599fc07f77b39adeb261b2efb2b70c107d8d7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1061" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 19 Oct 2012 11:04:19 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:18:05 GMT" + }, + { + "age": "1303243" + }, + { + "x-amz-cf-id": "mAtXqkAkC4DjophBzYEW5S6QprX5KyDZpPzyK9EozCLiukdFrBze5A==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 338, + "wire": "88ed0f0d03383530e06196dc34fd2817d4d27eea08010a8015c00ae32253168dffdf6c96df3dbf4a019532db52820040a05cb8115c6c4a62d1bfdfde5586640275f75b077f02aee2ffb3dbdb547e9df7be4f0f690f0c37d677474d3bdcc880fbbce76c5eb51804efbbc6feda32fe87675f5378820fdcdb", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "850" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 19 Nov 2011 02:02:32 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:12:52 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "30279750" + }, + { + "x-amz-cf-id": "V9zouqOby7zTdw8N1UFD-7MjNT6Is1zC6qGyOi0cvSwTqMJZ1Qkygw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 339, + "wire": "88d20f0d83136e07e46196d07abe941094d444a820044a00371a76e05b53168dffe3e2e1d15585081d75a71d7f01addc275ba7dedf529b07ef8c3bc9ae5f5c8053f3c6f8787e44dbefe0ef160bd3687238bceacf2f445648f1cd041fdfde0f1397fe590ad61900e1a079e2dd9db0022ddb820045ff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2561" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 22 Oct 2012 01:47:15 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 21 Nov 2011 14:13:29 GMT" + }, + { + "age": "1077467" + }, + { + "x-amz-cf-id": "S275mzRyfiEZwFTcPfyW0eoYH91UX_599Ev_ECgM6b_xOLfjspcbHg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"31-ris0UMaL_SL500_SS100_#1\"" + } + ] + }, + { + "seqno": 340, + "wire": "88f40f0d03353634e76196df697e9413ea693f750400854086e36d5c69c53168dfe6e5e46c96c361be94034a65b6a50400814006e09db8c894c5a37f558613ecbc0105cf7f02abdaeee78f503bc9d438ff3cd86a561105fb3bc3d326a72995abc0e5b994e6c65987a05d3920da1b2d7b2083e3e2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "564" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 29 Nov 2011 11:54:46 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 04 Jun 2010 01:27:32 GMT" + }, + { + "age": "29380216" + }, + { + "x-amz-cf-id": "R7S8on0vdk1HXxrim-2c2Zh8aNdO6mf4C0WS3tKHegaM2jWsiM5epQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 341, + "wire": "885f87352398ac4c697f0f0d830b8fbdec6196df697e9413ea693f75040085408ae083704053168dffebeae96c96df3dbf4a019532db52820040a003700cdc69b53168df558613ecbaf382177f03aee7df476bedf0eddb0e6f7e8328a9c24c7dbf1d1e889ac7e7553b7af3b50b6d9f94b7a4f5157da7d70e0f714d041fe8e70f138efe421ff7251297859773ffd07f9f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1698" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 29 Nov 2011 12:21:20 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 01:03:45 GMT" + }, + { + "age": "29378622" + }, + { + "x-amz-cf-id": "YvMqD5UqqFKzy1f2mFcHqX7aM_4HxOmRkYus-RhWfCdy_pqhPAEz_g==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"11+dlfeUrBL#1\"" + } + ] + }, + { + "seqno": 342, + "wire": "88c20f0d03353538f06196dc34fd282029a88950400894086e32d5c65a53168dffefeeed6c96c361be94034a65b6a50400814006e09db81754c5a37f55850882d804f77f02ac2711b4d6cd02b5ec90dbc30bfb304b5cb51f7ccf0c1942f1b654ef89057f658b743ad2f37aed66c7d764107feceb408725453d44498f57842507417f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "558" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 20 Oct 2012 11:34:34 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Fri, 04 Jun 2010 01:27:17 GMT" + }, + { + "age": "1215028" + }, + { + "x-amz-cf-id": "cVa44QM2u8IAuUF9QEfpfnoTg8a0J18iQn7wd2DQr-jo-fY8BpiHkQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + } + ] + }, + { + "seqno": 343, + "wire": "88c70f0d83109c7bf56196dc34fd282794cb6d0a0801128015c641704253168dfff46c96df3dbf4a019532db52820040a0037000b81794c5a37ff4f3558579b03627817f03ad10b2dab66966932976f7fddd47e377d1267d51c73ebc1fa5bbba46b111d17ee41f2d06e3da98f475bacdd9a083f1f00f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "2268" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 28 Jul 2012 02:30:22 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 01:00:18 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "8505280" + }, + { + "x-amz-cf-id": "22Ju-KfgdJeRvZSlX5DsdLObbhPEZeBSd4Gc72ZIaWMiVqmbMkB3Bg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 344, + "wire": "88cb0f0d820b81408721eaa8a4498f5788ea52d6b0e83772ff6196df697e94136a6e2d6a0801128205c13d71a714c5a37f7685dc5b3b96cf6c96df3dbf4a019532db52820040a05cb816ee09b53168df5891a47e561cc581c640e8800003eabb63a0c46496e4593e940bca681fa5040659500cdc659b820298b46f5586659684fbae7f7f06aded4ddd77fa8b2e6e23cbb6beeec2d533cd6e3d536e876084d7094f2ebd3aa7e64761fc47e89ecf87f342db20837caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "161" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 25 Sep 2012 20:28:46 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:15:25 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "3342976" + }, + { + "x-amz-cf-id": "qmBPDk2JKVaJRpv7A4mhguHOgSAQ224UfofPNOhYc7AXsZ28LFXM-Q==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 345, + "wire": "88f10f0d830b6e3bc76196dc34fd2827d4dc5ad4100225022b817ae34ca98b46ffc6c4c36c96df3dbf4a019532db52820040a05cb816ae01953168df5585640271d75f7f04ae7c758fd585db9fb67d3ddbddadfcda773782ddddf9b68d7c6fe33df9a3a33639b849d785e3f0bf865d77a3f1041fc3c2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1567" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 29 Sep 2012 12:18:43 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:14:03 GMT" + }, + { + "age": "3026779" + }, + { + "x-amz-cf-id": "9apayreRLqLNv5SP9KNS5EuSvY5sPVDHoDgblKHgUdkUCoUDFfPCbw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 346, + "wire": "88d90f0d83101d6fcb6196e4593e9403ca612c6a0801128066e09bb8d814c5a37fcac8c76c96df3dbf4a019532db52820040a05cb817ee01f53168df5586132275c65b177f02acf2e9666a5e7cfaefe558f961c0d55a5dba91f6dbe8dfb2e9ee35bb9c8676b67f3dc729abec17d1debcdb2083c7c60f138efe421ff7251297859773ffd07f9f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "2075" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 08 Feb 2012 03:25:50 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:19:09 GMT" + }, + { + "age": "23276352" + }, + { + "x-amz-cf-id": "x7eg4fYYkTWpaWFE4nN7BtaqRyiZfNva-voci7p3Xzbfipq19svpKQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"11+dlfeUrBL#1\"" + } + ] + }, + { + "seqno": 347, + "wire": "88dd0f0d830ba067cf6196df697e9413ea693f75040085403f71915c034a62d1bfce6c96df3dbf4a019532db52820040a05cb816ae32fa98b46fcdcc558613ecbcf3aebd7f02addd6c8eefd11038603f73f7cbe7978dd1cc53f3dff84879c3dc2d629e4dae333e5fdd5d0b778734774e6edb2083cbca0f138efe4015bf2dc7678263cfff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1703" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 29 Nov 2011 09:32:04 GMT" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:14:39 GMT" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "age": "29388778" + }, + { + "x-amz-cf-id": "Sud7TM_0UEovovJxWwSbgeoYTXcAYAv14GhdR63hJZOjeBUYsvtKqQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"01-XuHrwcHL#1\"" + } + ] + }, + { + "seqno": 348, + "wire": "88e10f0d8313800fd36196e4593e94136a65b685040089408ae36cdc65a53168dfd2d0cf6c96df3dbf4a019532db52820040a05cb8176e32e298b46f558579d13a079e7f02ada61ea0d5a9c2f7ba7bdafa61f3565ec6b31c4f773d229bc63c177d22f7e93b6c97617a501b2b67562be1d9041fcfce", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "2601" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 25 Jul 2012 12:53:34 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:17:36 GMT" + }, + { + "age": "8727088" + }, + { + "x-amz-cf-id": "mAk0OO6evBoCPjFxnJqirH_8vom2gwHEBysCZcqQfQejl1rp3OGD1Q==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 349, + "wire": "88e50f0d830b6f07d76196d07abe941094d444a820044a04371b76e36053168dffd6d4d36c96df3dbf4a019532db52820040a05cb8166e002a62d1bf5585081a03cc8b7f02acbe68e2c74929993cb4e2f7a538bbb385cfd87f0de720e6f5d701874e0bb4a4478f867303145eac8cf3f8820fd3d2e00f138efe401ff79b5b24c97f8e7ffa0ff3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1581" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 22 Oct 2012 11:57:50 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:13:01 GMT" + }, + { + "age": "1040832" + }, + { + "x-amz-cf-id": "Dib_HmcmgtWNGzNtGv3F6ZAXixIagykEiamEBmt2obULi0G_yrbohw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "cneonction": "close" + }, + { + "etag": "\"01+KP3cIDVL#1\"" + } + ] + }, + { + "seqno": 350, + "wire": "88e90f0d023434db6196dc34fd282029a8895040089400ae34ddc65b53168dffdad8d76c96df3dbf4a019532db52820040a05cb8166e34fa98b46f5585089a71d71d7f02aef1d2964b2f5d83877f1e5c4ffb93cfe73f195f9dd8b865e3b395d2faf1d62fc2f7aba72d4b0f13727e700dbe2083d7d6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "44" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 20 Oct 2012 02:45:35 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 16:13:49 GMT" + }, + { + "age": "1246767" + }, + { + "x-amz-cf-id": "wjm3efkQaATVWVoZIxXYwJ9h7_UJVQWBeywk_XevnjWO-aG5dXU1uw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 351, + "wire": "88dd6c96df3dbf4a044a681d8a08007d410ae081702da98b46ff5f911d75d0620d263d4c795ba0fb8d04b0d5a77b8b84842d695b05443c86aa6f5a839bd9ab5893aed8e8313e94a47e561cc581c0b2cbcdb2f3ff6496dd6d5f4a042a435d8a080c894106e36d5c6c2a62d1bf6196dc34fd280654d27eea0801128166e01bb801298b46ff0f0d83085c77e60f138efe421fd67f7b9b14fdb3ffd07f9f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Thu, 12 Mar 2009 22:20:15 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=613385389" + }, + { + "expires": "Sun, 11 Apr 2032 21:54:51 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:05:02 GMT" + }, + { + "content-length": "1167" + }, + { + "connection": "keep-alive" + }, + { + "etag": "\"11Z3ZviGhqL#1\"" + } + ] + }, + { + "seqno": 352, + "wire": "88bee44088f2b0e9f6b1a4583f91069060deedffcda0fd06e7db7bf2fef17f4003703370ff12acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7eaf6b83f9bd0ea52feed6a67879297b86d521bfa14c9c613a9938df3a97b5693a9ab7eb3a9ab86d52fe0ce6535f0ba65356fda652ef0dca6bc7cd4d5a73a9c34e4535f0daa61c9a54bdab429a61e2a64d3bd4bf834297b4ef5376f854c7821535edc0a67d5794c5ab8a9ab7de53f94088f2b0e9f6b1a4585fb3b38c7e336876de8f91ecdaf3f3a76c3c52d75e7b64d18bdf6c0fcee6ff44ff7c09f97dcf409dca9b487656814989aa005b3a557b9384842d695b05443c86aa6fae082d8b43316a4fc55f96497ca589d34d1f6a1271d882a60c9bb52cf3cdbeb07f0f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dff798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:05:02 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "0N0ET7DXR0Z0S958XDT2" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "rVbwKM7uj9c8KPLYmRAVt4kYRdMGzqE9h6Tyc+UcXD6y0h6n5t1Qps2dG4l0erjn" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html; charset=ISO-8859-1" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 353, + "wire": "885f88352398ac74acb37f0f0d830b417fed6196d07abe941054d27eea08010a816ae05fb800a98b46ffeceae90f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96d07abe941054d27eea08010a816ae059b827d4c5a37f55866400704eb82f7f10ace2e881b676b8fe78cf58fbcc404decf35e87b925b5e2f4b56707bf84dd7f419b8f648afd252b99f6c70c107fe9e8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1419" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 21 Nov 2011 14:19:01 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Mon, 21 Nov 2011 14:13:29 GMT" + }, + { + "age": "30062762" + }, + { + "x-amz-cf-id": "V720Rh4VXwLpavgc0gzogCAvcfu8eju-6aTUgkZ0KVqt2Dmee6LRbA==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 354, + "wire": "88c20f0d8365c033f16196dc34fd280654d27eea0801128072e001702153168dfff0eeed6c96df697e94038a435d8a0801028266e04171b794c5a37f5584136d3e2f7f02ad976460a8d5e84fe65b79c793f7d6ecd76fed459d68cb6f777b43d733df8887ce99281f9b6d662d31bcf0a1820fedec0f13a2fe5a0ffb73f38c866e9cf16efc65c8b77365c8af6dfa07d03e9973e99722ffa0ff3f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3603" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 03 Nov 2012 06:00:11 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Tue, 06 Apr 2010 23:10:58 GMT" + }, + { + "age": "25492" + }, + { + "x-amz-cf-id": "fQb0nipMtXJuYbIZySKBDRsrklJuv7qAkK8XsAxNdlaxuu3_Nb882A==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"41+6XVdi5mL_SX36_SY36_CR,0,0,36,36_#1\"" + } + ] + }, + { + "seqno": 355, + "wire": "885f87352398ac4c697f0f0d827020f66196e4593e940814d444a820044a01bb807ee01e53168dfff5f3f26c96df3dbf4a019532db52820040a00371905c03ca62d1bf5585104020b6df7f03adbc3fcf5539aa502745719f20b2eeda5dc799dd5fa59a9fcddd458cb8a206ffd5b17e66a8bab4b81f096b64107ff2f10f138efe421ff7251297859773ffd07f9f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "610" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 10 Oct 2012 05:09:08 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 01:30:08 GMT" + }, + { + "age": "2102155" + }, + { + "x-amz-cf-id": "CaXyn6Of0tMpboI2JSReSog7OZegmXSk2HeG_0TZ-GXKneON61wt4Q==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"11+dlfeUrBL#1\"" + } + ] + }, + { + "seqno": 356, + "wire": "88cb0f0d83700e33408721eaa8a4498f5788ea52d6b0e83772ff6196df697e9413ca6e2d6a080102816ae05fb8d32a62d1bf7685dc5b3b96cf588ca47e561cc581c640e88000036496e4593e940bca681fa5040659500cdc659b820298b46f6c96dd6d5f4a0995340fd2820040a01db8c86e32253168df558671c138d322077f06acfc3375a2889bb62c30bd862279ac376699bcbb3b17afbe9eff2d8fd79ebd9cd463e36fbe51c5e6ba1d8e68207caf0ae050a065c0c8269b00da7df6e47c5238e06395c8de136590ab9283db24b61ea4af5152a7f57a83db261b0f527fbf4085f2b10649cb8ec664a92d87a542507b6496c3d49f0f1397fe590ad61900e1a079e2dd9db0022ddb820045ff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6063" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 28 Sep 2010 14:19:43 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Sun, 23 May 2010 07:31:32 GMT" + }, + { + "age": "66264320" + }, + { + "x-amz-cf-id": "Xi5psl_5u_FA8F_cxp1Bgg5JQqekzjzXubyxkq6OioH5vJa_xpl7bg==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"31-ris0UMaL_SL500_SS100_#1\"" + } + ] + }, + { + "seqno": 357, + "wire": "88d50f0d8365e03dc76196d07abe94132a651d4a0801128205c6dbb827d4c5a37fc65891a47e561cc581c640e8800003eabb63a0c4c56c96e4593e9413ca6e2d6a08010a807ae005719754c5a37f5586134dbedbaeb57f05aee77b0793bdb2f7270cf46d336660f2cd247db119abb76c71d4dfd9df1e62ddf5c3574c3fbcbaf3262a7771f1041fc4c3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3808" + }, + { + "connection": "keep-alive" + }, + { + "date": "Mon, 23 Jan 2012 20:55:29 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 28 Sep 2011 08:02:37 GMT" + }, + { + "age": "24595774" + }, + { + "x-amz-cf-id": "YCExo8QCW6i8b43rK1WKdbqGi4BBr67tDQvHKeByUOjFZWkYcGmSVw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 358, + "wire": "88da0f0d8369d699cc6196df3dbf4a05c530963504008940bd71a66e32fa98b46fcbc2c96c96d07abe941054d27eea08010a816ae059b8d3aa62d1bf5586109b640079af7f02ae956fdd3cb2e5a25d9cb903d66f7b2476dbdb78651e8bbe658add0e9b18658f35dd326cd934de3d2d2e89f46c820fc8c7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4743" + }, + { + "connection": "keep-alive" + }, + { + "date": "Thu, 16 Feb 2012 18:43:39 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 21 Nov 2011 14:13:47 GMT" + }, + { + "age": "22530084" + }, + { + "x-amz-cf-id": "f-ZNWJJlfQWW0yKzQd7uCRUJaMBxf_uM7iH1fbKBNdQQggwy-fMhMQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 359, + "wire": "88de0f0d836da659d06196df697e94081486bb1410022500cdc10ae042a62d1bffcfc6cd6c96e4593e940b6a612c6a0801128172e34cdc03ea62d1bf55860baf85f75d177f02ad92234adbafeb01240f166af7c3f0df9f89bfbfd51b33e8f61fc5bdf0d7d4567a75fc5155c39fe2da41643b2083cccb", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5433" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 10 Apr 2012 03:22:11 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Wed, 15 Feb 2012 16:43:09 GMT" + }, + { + "age": "17919772" + }, + { + "x-amz-cf-id": "d_if579P0cd1V3nzUXiXXtDTylQLMz1X-zUPk2ry79G_nUYX-N0rAQ==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 360, + "wire": "88e20f0d8369a69fd46196c361be94132a681d8a0801128005c69bb8d32a62d1bfd3cad10f1394fe5a0f3fdcfd727fbe08cf16ece165b8bfe83fcf6c96c361be9403ea6e2d6a08010a8076e001704f298b46ff55860bed3cd32e037f02adfcc71feeed15a35e8c3cf5e2c65a3d76d9df99c2cb2b39c30bd57bcf96c969b6d8e58dea64888cf8cda5ef1041d0cf", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4449" + }, + { + "connection": "keep-alive" + }, + { + "date": "Fri, 23 Mar 2012 00:45:43 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "etag": "\"41YZLkI+UsL_SL135_#1\"" + }, + { + "last-modified": "Fri, 09 Sep 2011 07:00:28 GMT" + }, + { + "age": "19484360" + }, + { + "x-amz-cf-id": "XHbZSMpsPMFYPGHelyqQvYo133-6UF8nzLJrfmuubfb8md_c3wKN8w==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 361, + "wire": "88e60f0d83680e37d86196df697e94136a6e2d6a0801128166e01db82754c5a37fd7ced56c96df3dbf4a01a530963504008140b971a66e36da98b46f558665971f69b73f7f02ad276efcd3fb826f734be373a787bf6d5d6df7b4dee7dc57c766726dc43d6dd556111bdf82931eef1b96bde2083fd4d30f1397fe590ad61900e1a079e2dd9db0022ddb820045ff41fe7f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4065" + }, + { + "connection": "keep-alive" + }, + { + "date": "Tue, 25 Sep 2012 13:07:27 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Thu, 04 Feb 2010 16:43:55 GMT" + }, + { + "age": "3369456" + }, + { + "x-amz-cf-id": "cqvYtZEgzgfwS7oAvqOkuzRizhSe9arLcRGaP5nnF2izwecHSwS-Cw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"31-ris0UMaL_SL500_SS100_#1\"" + } + ] + }, + { + "seqno": 362, + "wire": "88ea0f0d8369d75edc6196e4593e940b4a6e2d6a08010a8176e360b817d4c5a37fdbd2d96c96d07abe94134a436cca08007d40b971a7ae084a62d1bf558665b7c4d89e6b7f02adf1f9cdfbf7e3e1663ca7ddced9746ba5382cdbb6d4f3eb5b2226af418c0ec5a49fb864e1b897c79f56dafc4107d8d70f13a2fe5a0ffb73f38c866e9cf16efc65c8b77365c8af6dfa07d03e9973e99722ffa0ff3f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4778" + }, + { + "connection": "keep-alive" + }, + { + "date": "Wed, 14 Sep 2011 17:50:19 GMT" + }, + { + "server": "Server" + }, + { + "cache-control": "max-age=630720000,public" + }, + { + "expires": "Wed, 18 May 2033 03:33:20 GMT" + }, + { + "last-modified": "Mon, 24 Aug 2009 16:48:22 GMT" + }, + { + "age": "35925284" + }, + { + "x-amz-cf-id": "wXY9DDbUrHJoSYufMPmtErRRutYkp32cOy1b07_NcZFdUScDaLORpw==" + }, + { + "via": "1.0 e0361d2450a4995d92d661bf6b825ede.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + }, + { + "etag": "\"41+6XVdi5mL_SX36_SY36_CR,0,0,36,36_#1\"" + } + ] + }, + { + "seqno": 363, + "wire": "88de6c96e4593e941094dc5ad410020504cdc6dfb8d3ca62d1bf5f911d75d0620d263d4c795ba0fb8d04b0d5a77b8b84842d695b05443c86aa6f5a839bd9ab5893aed8e8313e94a47e561cc581c0b2cb6f38d07f6496dd6d5f4a042a435d8a080c8940b5704fdc032a62d1bf6196dc34fd280654d27eea0801128166e01bb801298b46ff0f0d84109d701fe7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Server" + }, + { + "last-modified": "Wed, 22 Sep 2010 23:59:48 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=613358641" + }, + { + "expires": "Sun, 11 Apr 2032 14:29:03 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:05:02 GMT" + }, + { + "content-length": "22760" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 364, + "wire": "886196dc34fd280654d27eea0801128166e01bb80654c5a37fe64088f2b0e9f6b1a4583f91068737e5c0ce1fde7bff36e9dbcdb08b9f4003703370ff12acf4189eac2cb07f33a535dc618f1e3c2e3a47ecf52e43d2c78648c56cd6bf9a68fe7eaf6b83f9bd0ea52feed6a67879297b86d521bfa14c9c613a9938df3a97b5693a9ab7eb3a9ab86d52fe0ce6535f0ba65356fda652ef0dca6bc7cd4d5a73a9c34e4535f0daa61c9a54bdab429a61e2a64d3bd4bf834297b4ef5376f854c7821535edc0a67d5794c5ab8a9ab7de53f94088f2b0e9f6b1a4585fb3b7ab09bfd270876dde993bc9dc18ba3f9785ec4c687cc6b2da317bd0f6a9f6f7e9c98ec49da9f5d85c12bb535c6af64f70d8e77b9384842d695b05443c86aa6fae082d8b43316a4fc6f10f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dff798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:05:03 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "0M6TJE3FZYTXRNRY512Y" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "uk/tDjh11RBjIvdv0Gj9JUCG/M9iirulGzM8OhRvjW/qch4hPreEf7n4VnzczAr6" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 365, + "wire": "88c3eb7f039105fd3c7161e38dabd85f7365dd2d6fd0ffc27f02b6ef83fd87d6e973f1629c16736c923a816993cf0d5ab570ef1f3bdc787ae77a4e9e49e63d2c97d6cdbd19d7e394f3dfeafffbf33ffbd9c1c9f40f28c74150831ea58d240175e59b7c4e09c12ccbae3ed32dfda958d33c0c7da921e919aa8171d23f67a9721e9fb50be6b3585441bed2fd2800ad94752c2032e2807ae001700153168dffc0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:05:03 GMT" + }, + { + "server": "Server" + }, + { + "x-amz-id-1": "0DNVGFVH4CF96QBN4TM9" + }, + { + "p3p": "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" + }, + { + "x-amz-id-2": "vE+1ySfLV/mErY5cd7s2NdxUOOOUvbYCVUyYCdjxcxbN3eyQRj3PwWhhDk9+xh+Q" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "set-cookie": "session-id=178-5926262-3769435; path=/; domain=.amazon.com; expires=Tue, 01-Jan-2036 08:00:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_22.json b/http/http-client/src/test/resources/hpack-test-case/story_22.json new file mode 100644 index 000000000..7250f4903 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_22.json @@ -0,0 +1,15857 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "886196dc34fd280654d27eea0801128115c6dcb8c814c5a37f768586b19272ff588aa47e561cc581e71a003f6496dd6d5f4a01a5349fba820044a04571b72e32053168df6c96df697e940894ca3a9410020502cdc69eb800298b46ff0f138bfe5b0acd46d11d91f07f3f52848fd24a8f0f0d023831408721eaa8a4498f5788cc52d6b4341bb97f5f87497ca589d34d1f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:30 GMT" + }, + { + "server": "Apache" + }, + { + "cache-control": "max-age=86400" + }, + { + "expires": "Sun, 04 Nov 2012 12:56:30 GMT" + }, + { + "last-modified": "Tue, 12 Jan 2010 13:48:00 GMT" + }, + { + "etag": "\"51-4b4c7d90\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "81" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "text/html" + } + ] + }, + { + "seqno": 1, + "wire": "88c5c4c3c26c96d07abe94134a651d4a08010a810dc6c5700053168dff0f138bfe42c9566a466471d283f9c10f0d03333138c05f87497ca58ae819aa", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:30 GMT" + }, + { + "server": "Apache" + }, + { + "cache-control": "max-age=86400" + }, + { + "expires": "Sun, 04 Nov 2012 12:56:30 GMT" + }, + { + "last-modified": "Mon, 24 Jan 2011 11:52:00 GMT" + }, + { + "etag": "\"13e-4d3d67e0\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "318" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "text/plain" + } + ] + }, + { + "seqno": 2, + "wire": "886196dc34fd280654d27eea0801128115c6dcb8c894c5a37f7686bbcb73015c1f0f0d83680cb55f90497ca589d34d1f649c7620a98268faff5885aec3771a4b6496dc34fd280654d27eea0801128115c6dcb8c894c5a37f5a839bd9ab0f28d3bb0e4bfc325f82eb8165c86f04182ee0042f61bd7c417305d71abcd5e0c2ddeb9871401fb50be6b3585441b869fa500cada4fdd6684a04571b72e32253168dff6a5634cf031f6a487a466aa05e319a4b5721e940037033709ebdae0fe54d5bf2297f76b52f6adaa64e30a9ab86d53269bea5ed5a14fe7fc8", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:32 GMT" + }, + { + "server": "BWS/1.0" + }, + { + "content-length": "4034" + }, + { + "content-type": "text/html;charset=gbk" + }, + { + "cache-control": "private" + }, + { + "expires": "Sat, 03 Nov 2012 12:56:32 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "set-cookie": "BAIDUID=B6136AC10EBE0A8FCD216EB64C4C1A5C:FG=1; expires=Sat, 03-Nov-42 12:56:32 GMT; path=/; domain=.baidu.com" + }, + { + "p3p": "CP=\" OTI DSP COR IVA OUR IND COM \"" + }, + { + "connection": "Keep-Alive" + } + ] + }, + { + "seqno": 3, + "wire": "88c4cd6c96df3dbf4a080a651d4a08010a8076e05bb8cb6a62d1bf0f138ffe5c6cab34f8da095c6df659203f9fca0f0d830b8c83588ca47e561cc58190b6cb8000016496df697e940054d27eea0802128115c6dcb8c894c5a37fcb5f87352398ac4c697f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:32 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Thu, 20 Jan 2011 07:15:35 GMT" + }, + { + "etag": "\"65e-49a41e65933c0\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "1630" + }, + { + "cache-control": "max-age=315360000" + }, + { + "expires": "Tue, 01 Nov 2022 12:56:32 GMT" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 4, + "wire": "88c8d16c96df3dbf4a05f521aec5040089403f71b0dc1014c5a37f0f138efe5b8d66a3281b0c827197000fe7ce0f0d023931c1c0cdbf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:32 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Thu, 19 Apr 2012 09:51:20 GMT" + }, + { + "etag": "\"5b-4be051d263600\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "91" + }, + { + "cache-control": "max-age=315360000" + }, + { + "expires": "Tue, 01 Nov 2022 12:56:32 GMT" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 5, + "wire": "886196dc34fd280654d27eea0801128115c6dcb8cb2a62d1bfd3c40f28e8bb0e4bfc325f81f6a165cbe265f71e65c79dbacde7c4ebecc2215d84179f66e61c5007ed42f9acd615106eb6afa500cada4fdd60b2a04571b72e32ca98b46ffb5291f95873160642db2e0000fb52b1a67818fb5243d2335502f18cd25ab90f4fda9dcb620c7aa00f6c96df3dbf4a042a436cca08010a8076e34d5c642a62d1bf0f1390fe5b7647d6686365b95d7e37db203f9fd0c36496df697e940054d27eea0802128115c6dcb8cb2a62d1bf7b9384842d695b05443c86aa6fae082d8b43316a4fc80f0d83780007d15f901d75d0620d263d4c741f71a0961ab4ff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:33 GMT" + }, + { + "server": "Apache" + }, + { + "p3p": "CP=\" OTI DSP COR IVA OUR IND COM \"" + }, + { + "set-cookie": "BAIDUID=94A36D239683687B3C92793A22BA0C93:FG=1; expires=Sun, 03-Nov-13 12:56:33 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1" + }, + { + "last-modified": "Thu, 11 Aug 2011 07:44:31 GMT" + }, + { + "etag": "\"57d9-4aa35f79b95c0\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=315360000" + }, + { + "expires": "Tue, 01 Nov 2022 12:56:33 GMT" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "8000" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "application/javascript" + } + ] + }, + { + "seqno": 6, + "wire": "88c2d7c80f28e9bb0e4bfc325f80227e1bf75f15d699bd86f36276f42ee1bafb376f5d7a1baf3d730e2803f6a17cd66b0a88375b57d280656d27eeb0595022b8db9719654c5a37fda948fcac398b03216d9700007da958d33c0c7da921e919aa8178c6692d5c87a7ed4ee5b1063d50076c96c361be940094d27eea0801128072e36d5c6c0a62d1bf0f1390fe5b6db6d668923b23e4191e13c0fe7fd4c7c1c0ca0f0d8375a7dfd3bf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:33 GMT" + }, + { + "server": "Apache" + }, + { + "p3p": "CP=\" OTI DSP COR IVA OUR IND COM \"" + }, + { + "set-cookie": "BAIDUID=129ADB92B43CFC527CA7FB93BCB8AB88:FG=1; expires=Sun, 03-Nov-13 12:56:33 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1" + }, + { + "last-modified": "Fri, 02 Nov 2012 06:54:50 GMT" + }, + { + "etag": "\"5555-4cd7d9cac8280\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=315360000" + }, + { + "expires": "Tue, 01 Nov 2022 12:56:33 GMT" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "7499" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "application/javascript" + } + ] + }, + { + "seqno": 7, + "wire": "88c3d8c90f28e9bb0e4bfc325f82f5e1430c22870330e1640cc2fb4dde870bd81f7da6eede15fb9871401fb50be6b3585441badabe94032b693f7582ca8115c6dcb8cb2a62d1bfed4a47e561cc58190b6cb80003ed4ac699e063ed490f48cd540bc633496ae43d3f6a772d8831ea803f6c96df3dbf4a080a6e2d6a080112806ae322b8db6a62d1bf0f138ffe44e4a359a20c237e495c246407f3d5c8c2c1cb0f0d8365b79cd4c0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:33 GMT" + }, + { + "server": "Apache" + }, + { + "p3p": "CP=\" OTI DSP COR IVA OUR IND COM \"" + }, + { + "set-cookie": "BAIDUID=CC2AAA2AE3AF303A945CAF8E9945BC2D:FG=1; expires=Sun, 03-Nov-13 12:56:33 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1" + }, + { + "last-modified": "Thu, 20 Sep 2012 04:32:55 GMT" + }, + { + "etag": "\"26fa-4ca1a9df6cbc0\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=315360000" + }, + { + "expires": "Tue, 01 Nov 2022 12:56:33 GMT" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "3586" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "application/javascript" + } + ] + }, + { + "seqno": 8, + "wire": "88c4d9ca0f28e8bb0e4bfc325f81cbc1becb6f60699861be169903c0bc17b034fbc0659c2e86e61c5007ed42f9acd615106eb6afa500cada4fdd60b2a04571b72e32ca98b46ffb5291f95873160642db2e0000fb52b1a67818fb5243d2335502f18cd25ab90f4fda9dcb620c7aa00f6c96df3dbf4a320532db52820042a04171b72e36153168df0f138efe44dcab34370b190412342203f9d60f0d03363037c9c3d55f87352398ac5754df", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:33 GMT" + }, + { + "server": "Apache" + }, + { + "p3p": "CP=\" OTI DSP COR IVA OUR IND COM \"" + }, + { + "set-cookie": "BAIDUID=6C1D358E43AAD143080C18E498033F71:FG=1; expires=Sun, 03-Nov-13 12:56:33 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1" + }, + { + "last-modified": "Thu, 30 Jun 2011 10:56:51 GMT" + }, + { + "etag": "\"25f-4a6ebc21c42c0\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "607" + }, + { + "cache-control": "max-age=315360000" + }, + { + "expires": "Tue, 01 Nov 2022 12:56:33 GMT" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "image/png" + } + ] + }, + { + "seqno": 9, + "wire": "886196dc34fd280654d27eea0801128115c6dcb8cb4a62d1bfdc0f0d033134375f89352398ac7958c43d5fd1d0cf0f28d3bb0e4bfc325f82eb8165c86f04182ee0042f61bd7c417305d71abcd5e0c2ddeb9871401fb50be6b3585441b869fa500cada4fdd6684a04571b72e32253168dff6a5634cf031f6a487a466aa05e319a4b5721e9ced86c96d07abe94134a651d4a08010a810dc6c5700da98b46ff0f138ffe42c95669f1bee32378a065a07f3fdac6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:34 GMT" + }, + { + "server": "Apache" + }, + { + "content-length": "147" + }, + { + "content-type": "image/x-icon" + }, + { + "cache-control": "private" + }, + { + "expires": "Sat, 03 Nov 2012 12:56:32 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "set-cookie": "BAIDUID=B6136AC10EBE0A8FCD216EB64C4C1A5C:FG=1; expires=Sat, 03-Nov-42 12:56:32 GMT; path=/; domain=.baidu.com" + }, + { + "p3p": "CP=\" OTI DSP COR IVA OUR IND COM \"" + }, + { + "connection": "Keep-Alive" + }, + { + "last-modified": "Mon, 24 Jan 2011 11:52:05 GMT" + }, + { + "etag": "\"13e-49a963a8e0340\"" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding,User-Agent" + } + ] + }, + { + "seqno": 10, + "wire": "880f28e9bb0e4bfc325f81dbace103b7e17705d75b78379c861861bc265f7ef030b4e3f730e2803f6a523f2b0e62c0c85b65c0001f6a17cd66b0a88375b57d280656d27eeb0595022b8db97197d4c5a37fda921e919aa8179c6708995c87a7ed4ac699e063ed4ee5b1063d5007cf5f91497ca589d34d1f649c7620a98386fc2b3d6496dc34fd280654d27eea0801128115c6dcb8cbea62d1bf5887a47e561cc5801f7b8b84842d695b05443c86aa6fd4798624f6d5d4b27f6196dc34fd280654d27eea0801128115c6dcb8cbea62d1bfda", + "headers": [ + { + ":status": "200" + }, + { + "set-cookie": "BAIDUID=7B3F07DA7EB7581C6AAAAC2399C0F469:FG=1; max-age=31536000; expires=Sun, 03-Nov-13 12:56:39 GMT; domain=.hao123.com; path=/; version=1" + }, + { + "p3p": "CP=\" OTI DSP COR IVA OUR IND COM \"" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "expires": "Sat, 03 Nov 2012 12:56:39 GMT" + }, + { + "cache-control": "max-age=0" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 12:56:39 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 11, + "wire": "88c70f138afe44f3c065974226ff9fe06c96df697e940b4a612c6a0801128066e099b8cb8a62d1bf6496df697e9413ea6a22541002ca8115c6dcb8d014c5a37f588ba47e561cc58190840d00000f0d033739356196dc34fd280654d27eea0801128115c6dcb8d014c5a37fde", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "etag": "\"2880337125\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 14 Feb 2012 03:23:36 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:40 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "content-length": "795" + }, + { + "date": "Sat, 03 Nov 2012 12:56:40 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 12, + "wire": "88d50f138afe42fba07da71a6ddfe7e46c96df697e94105486d994100225022b817ee34ea98b46ffc1c00f0d023439bfdf", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "etag": "\"1970946457\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 21 Aug 2012 12:19:47 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:40 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "content-length": "49" + }, + { + "date": "Sat, 03 Nov 2012 12:56:40 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 13, + "wire": "88cc0f138afe596c0f0990b4d39fcfe56c96e4593e940894dc5ad410022500e5c6dfb8c814c5a37fc2c10f0d8369a71bc0e0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "etag": "\"3508231446\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Wed, 12 Sep 2012 06:59:30 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:40 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "content-length": "4465" + }, + { + "date": "Sat, 03 Nov 2012 12:56:40 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 14, + "wire": "887688cbbb58980ae05c5f6196dc34fd280654d27eea0801128115c6dcb8d054c5a37f5f88352398ac74acb37f7f2988ea52d6b0e83772ff0f0d836df13d588ca47e561cc58190b6cb80003f0f1390fe59085c644dbc07c2d3ad3ae05f7bf96496dd6d5f4a0195349fba8200595002b807ee05b53168df6c96dc34fd280654d27eea0801128015c03b719654c5a37f", + "headers": [ + { + ":status": "200" + }, + { + "server": "JSP2/1.0.2" + }, + { + "date": "Sat, 03 Nov 2012 12:56:41 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "5928" + }, + { + "cache-control": "max-age=31536000" + }, + { + "etag": "\"3116325809147476198\"" + }, + { + "expires": "Sun, 03 Nov 2013 02:09:15 GMT" + }, + { + "last-modified": "Sat, 03 Nov 2012 02:07:33 GMT" + } + ] + }, + { + "seqno": 15, + "wire": "88c4c3c2c10f0d84081b6c1fc00f1391fe42cbc165b79f132075f0b420040f7f3f6496c361be940054d27eea0801654082e36cdc69d53168df6c96df3dbf4a002a693f750400894039700ddc034a62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "JSP2/1.0.2" + }, + { + "date": "Sat, 03 Nov 2012 12:56:41 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "10550" + }, + { + "cache-control": "max-age=31536000" + }, + { + "etag": "\"13813589230791420108\"" + }, + { + "expires": "Fri, 01 Nov 2013 10:53:47 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 06:05:04 GMT" + } + ] + }, + { + "seqno": 16, + "wire": "885f86497ca582211f0f1389fe5f7c2dbc069b0ff3f06c96c361be940094d27eea080112806ee01db80694c5a37fcdccd1e70f0d840840f07fcbeb", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/css" + }, + { + "etag": "\"991580451\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Fri, 02 Nov 2012 05:07:04 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:40 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "11081" + }, + { + "date": "Sat, 03 Nov 2012 12:56:40 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 17, + "wire": "88d80f138afe44f09c6de744cbdfcff16c96df697e940bca6e2d6a0801128072e36fdc13ca62d1bf6496df697e9413ea6a22541002ca8115c6dcb8d054c5a37fce0f0d830bc067c9ed", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "etag": "\"2826587238\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 18 Sep 2012 06:59:28 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:41 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "content-length": "1803" + }, + { + "date": "Sat, 03 Nov 2012 12:56:41 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 18, + "wire": "88da0f138afe44f080271a7de67f9ff36c96df697e940bca6e2d6a0801128015c69bb81754c5a37fbfcf0f0d8365d033caee", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "etag": "\"2820264983\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 18 Sep 2012 02:45:17 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:41 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "content-length": "3703" + }, + { + "date": "Sat, 03 Nov 2012 12:56:41 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 19, + "wire": "88db0f1389fe5d75f005a6402fe7f46c96d07abe94038a436cca0801128105c0bb71b0298b46ffc0d00f0d83101b67cbef", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "etag": "\"779014302\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Mon, 06 Aug 2012 10:17:50 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:41 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "content-length": "2053" + }, + { + "date": "Sat, 03 Nov 2012 12:56:41 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 20, + "wire": "885f8b497ca58e83ee3412c3569f0f1389fe5a65f03627dc73f9f66c96c361be940094d27eea080112806ee01db80654c5a37fd3d2d7ed0f0d8465e700ffd1f1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/javascript" + }, + { + "etag": "\"439052966\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Fri, 02 Nov 2012 05:07:03 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:40 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "38609" + }, + { + "date": "Sat, 03 Nov 2012 12:56:40 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 21, + "wire": "88de0f138afe42d81913ecb6dbdfcff7bec2d2d7ed0f0d840b2175afcdf1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "etag": "\"1503293558\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Fri, 02 Nov 2012 05:07:03 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:41 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "13174" + }, + { + "date": "Sat, 03 Nov 2012 12:56:41 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 22, + "wire": "88bf0f1389fe5d7c0269b7840fe7f7bed3d2d7ed0f0d846596d973d1f1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/javascript" + }, + { + "etag": "\"790245820\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Fri, 02 Nov 2012 05:07:03 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:40 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "33536" + }, + { + "date": "Sat, 03 Nov 2012 12:56:40 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 23, + "wire": "88de0f138afe42165c65a6da033fcff76c96e4593e940baa6a225410022502edc03d700253168dff6496df697e9413ea6a22541002ca8115c6dcb8d32a62d1bfd40f0d8365d6436196dc34fd280654d27eea0801128115c6dcb8d32a62d1bff4", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "etag": "\"1136345403\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Wed, 17 Oct 2012 17:08:02 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:43 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "content-length": "3731" + }, + { + "date": "Sat, 03 Nov 2012 12:56:43 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 24, + "wire": "88e10f138afe42f3c1784265b67f9ffa6c96df697e940b8a6a225410022502ddc13d719714c5a37fc0d6dbf10f0d830800efbff5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "etag": "\"1881822353\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 16 Oct 2012 15:28:36 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:43 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1007" + }, + { + "date": "Sat, 03 Nov 2012 12:56:43 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 25, + "wire": "88c30f1389fe5b6d9005a705fcfffb6c96c361be940bea6a225410022502ddc6dfb81754c5a37fc1d7dcf20f0d83105d7fc0f6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/javascript" + }, + { + "etag": "\"55301462\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Fri, 19 Oct 2012 15:59:17 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:43 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "2179" + }, + { + "date": "Sat, 03 Nov 2012 12:56:43 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 26, + "wire": "880f28e9bb0e4bfc325f81dbace103b7e17705d75b78379c861861bc265f7ef030b4e3f730e2803f6a523f2b0e62c0c85b65c0001f6a17cd66b0a88375b57d280656d27eeb0595022b8db97197d4c5a37fda921e919aa8179c6708995c87a7ed4ac699e063ed4ee5b1063d5007f1dfc1d7dcf2dbc0f6ed0f138afe44078417db784f7f3ffc6c96df697e940bca651d4a08010a8072e32fdc0094c5a37f0f0d023433", + "headers": [ + { + ":status": "200" + }, + { + "set-cookie": "BAIDUID=7B3F07DA7EB7581C6AAAAC2399C0F469:FG=1; max-age=31536000; expires=Sun, 03-Nov-13 12:56:39 GMT; domain=.hao123.com; path=/; version=1" + }, + { + "p3p": "CP=\" OTI DSP COR IVA OUR IND COM \"" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:43 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 12:56:43 GMT" + }, + { + "server": "BWS/1.0" + }, + { + "content-type": "image/gif" + }, + { + "etag": "\"2082195828\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 18 Jan 2011 06:39:02 GMT" + }, + { + "content-length": "43" + } + ] + }, + { + "seqno": 27, + "wire": "88ee0f138afe44078417db784f7f3f52848fd24a8fbfc3d90f0d023433c2f8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "etag": "\"2082195828\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 18 Jan 2011 06:39:02 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:43 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 12:56:43 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 28, + "wire": "88c60f138afe42113e0699032dff3fbe6c96c361be940bea6a225410022502ddc6deb8d814c5a37fc4dadff50f0d830bee0bc3f9", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/javascript" + }, + { + "etag": "\"1129043035\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Fri, 19 Oct 2012 15:58:50 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:43 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1962" + }, + { + "date": "Sat, 03 Nov 2012 12:56:43 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 29, + "wire": "88d6c3e6d30f0d83081d730f138afe44cb2079d0becb3fcfbf6c96c361be940b2a65b6850400894033702edc684a62d1bf6496dc34fd281129947528200595021b8c86e01b53168dff588ca47e561cc5802db6d880007f", + "headers": [ + { + ":status": "200" + }, + { + "server": "JSP2/1.0.2" + }, + { + "date": "Sat, 03 Nov 2012 12:56:43 GMT" + }, + { + "content-type": "image/png" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "1076" + }, + { + "etag": "\"2330871933\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Fri, 13 Jul 2012 03:17:42 GMT" + }, + { + "expires": "Sat, 12 Jan 2013 11:31:05 GMT" + }, + { + "cache-control": "max-age=15552000" + } + ] + }, + { + "seqno": 30, + "wire": "88f30f138afe44079a69b71e719fe7c26c96df697e940bca651d4a08010a8066e005702fa98b46ffc8de0f0d023433c77686bbcb73015c1f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "etag": "\"2084456863\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 18 Jan 2011 03:02:19 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:43 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 12:56:43 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 31, + "wire": "886196dc34fd280654d27eea0801128115c6dcb8d34a62d1bf768fc17b568521ac649caa05702e1657075a839bd9abe65f87497ca589d34d1f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:44 GMT" + }, + { + "server": "ECOM Apache 1.0.13.0" + }, + { + "content-encoding": "gzip" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-type": "text/html" + } + ] + }, + { + "seqno": 32, + "wire": "884085aec1cd48ff86a8eb10649cbfeafa0f138afe42f81b79d00421fe7fc96c96e4593e940bca693f7504003ea01fb8d35700fa98b46f6496dc34fd280654d27eea0801128115c6dcb8d34a62d1bf0f0d0130c4c5", + "headers": [ + { + ":status": "200" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "max-age=0" + }, + { + "content-type": "image/gif" + }, + { + "etag": "\"1905870111\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Wed, 18 Nov 2009 09:44:09 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 12:56:44 GMT" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 12:56:44 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 33, + "wire": "887689aa6355e580ae05c2df6196dc34fd280654d27eea0801128115c6ddb81794c5a37ff0ece17f0386d27588324e5f5886a8eb10649cbf6496df3dbf4a002a651d4a05f740a0017000b800298b46ff0f28a7cbbb06edd93569c97e01342038d34e3616aeb3780204379b03e17eebecb206c0cfda9ac699e063c7f0", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx/1.0.15" + }, + { + "date": "Sat, 03 Nov 2012 12:57:18 GMT" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "pragma": "No-cache" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "set-cookie": "JSESSIONID=24206446514B3C020AC50919B9330503; Path=/" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 34, + "wire": "884089f2b567f05b0b22d1fa87d78f5b0dae25d9588aa47e561cc5804dbe2003c76496df697e94038a693f75040089408ae36e5c69c53168dfdb0f28cca0e4802abb795ba156e855bb81585f55dbcadd0ab742addc0ac2ffda85f359ac2a20df697e94038b693f75840089408ae36e5c69c53168dff6a5634cf031f6a487a466aa05e719c2265721e9f3ca0f0d033638346196dc34fd280654d27eea0801128115c6dcb8d38a62d1bf7686a0d34e94d727", + "headers": [ + { + ":status": "200" + }, + { + "x-powered-by": "PHP/5.2.3" + }, + { + "cache-control": "max-age=259200" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Tue, 06 Nov 2012 12:56:46 GMT" + }, + { + "content-type": "text/javascript" + }, + { + "set-cookie": "loc=1%7C%B1%B1%BE%A9%7C%B1%B1%BE%A9; expires=Tue, 06-Nov-2012 12:56:46 GMT; path=/; domain=.hao123.com" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "684" + }, + { + "date": "Sat, 03 Nov 2012 12:56:46 GMT" + }, + { + "server": "lighttpd" + } + ] + }, + { + "seqno": 35, + "wire": "884084a4b2187f84a4b2187fe96496c361be94138a6a2254100225022b826ae05953168dff6c96dc34fd2826d486bb141000fa8076e01ab800298b46ffedc276841d6324e50f0d8379f7c5", + "headers": [ + { + ":status": "200" + }, + { + "media": "media" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Fri, 26 Oct 2012 12:24:13 GMT" + }, + { + "last-modified": "Sat, 25 Apr 2009 07:04:00 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "date": "Sat, 03 Nov 2012 12:56:46 GMT" + }, + { + "server": "apache" + }, + { + "content-length": "8992" + } + ] + }, + { + "seqno": 36, + "wire": "887688aa6355e580ae05c16196dc34fd280654d27eea0801128115c6dcb8d3aa62d1bf5f911d75d0620d263d4c795ba0fb8d04b0d5a76c96df3dbf4a002a693f750400894082e001704053168dfffcf16496dc34fd280654d27eea0801128166e09cb8d3aa62d1bf5889a47e561cc5802f001f0f28c4348dbd001b2e9c145ee38723e47b1cbd42e70d10cd041f6a17cd66b0a8837da5fa50015b49fbac2128115c6dcb8d3aa62d1bfed490f48cd540dbcb90f4fda958d33c0c7f4003703370adacf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe6f70daa437f429ab86d534eadaa6edf0a9a725ffe7d7", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx/1.0.0" + }, + { + "date": "Sat, 03 Nov 2012 12:56:47 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Thu, 01 Nov 2012 10:00:20 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "expires": "Sat, 03 Nov 2012 13:26:47 GMT" + }, + { + "cache-control": "max-age=1800" + }, + { + "set-cookie": "id58=05eNElCVFI9c8Hfk16UMAg==; expires=Tue, 01-Nov-22 12:56:47 GMT; domain=58.com; path=/" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"CUR ADM OUR NOR STA NID\"" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 37, + "wire": "885f87352398ac5754df0f138afe42f3400b61032cff3fe16c96df697e940b4a612c6a0801128066e099b8cb8a62d1bf6496df697e9413ea6a22541002ca8115c6dcb8d3aa62d1bf588ba47e561cc58190840d00007b8b84842d695b05443c86aa6fdc0f0d8369f6dfc8df", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "etag": "\"1840151033\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 14 Feb 2012 03:23:36 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:47 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "4959" + }, + { + "date": "Sat, 03 Nov 2012 12:56:47 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 38, + "wire": "88c20f138afe42265c0ba1132cff3fe5c1c0bfbedc0f0d8369b743c8df", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "etag": "\"1236171233\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 14 Feb 2012 03:23:36 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:47 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "4571" + }, + { + "date": "Sat, 03 Nov 2012 12:56:47 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 39, + "wire": "880f28e9bb0e4bfc325f81dbace103b7e17705d75b78379c861861bc265f7ef030b4e3f730e2803f6a523f2b0e62c0c85b65c0001f6a17cd66b0a88375b57d280656d27eeb0595022b8db97197d4c5a37fda921e919aa8179c6708995c87a7ed4ac699e063ed4ee5b1063d50077f049ebdae0fe54d5bf2297f76b52f6adaa64e30a9ab86d53269bea5ed5a14fe7f5f91497ca589d34d1f649c7620a98386fc2b3dc2c1c0de798624f6d5d4b27fcbe25f89352398ac7958c43d5f0f1389fe5b71d0ba2138fff3e96c96df3dbf4a002a681d8a0801128015c64371b794c5a37f0f0d83085b07", + "headers": [ + { + ":status": "200" + }, + { + "set-cookie": "BAIDUID=7B3F07DA7EB7581C6AAAAC2399C0F469:FG=1; max-age=31536000; expires=Sun, 03-Nov-13 12:56:39 GMT; domain=.hao123.com; path=/; version=1" + }, + { + "p3p": "CP=\" OTI DSP COR IVA OUR IND COM \"" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "expires": "Tue, 29 Oct 2013 12:56:47 GMT" + }, + { + "cache-control": "max-age=31104000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 12:56:47 GMT" + }, + { + "server": "BWS/1.0" + }, + { + "content-type": "image/x-icon" + }, + { + "etag": "\"567172269\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Thu, 01 Mar 2012 02:31:58 GMT" + }, + { + "content-length": "1150" + } + ] + }, + { + "seqno": 40, + "wire": "886196dc34fd280654d27eea0801128115c6dab82754c5a37f7689aa6355e580ae05c20f5f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ed424e3b1054c16a6559efc36c96df3dbf4a05c521b665040089403d71976e36d298b46f5888a47e561cc5819003e65501314084f2b7730fdd0ae1523e9352264571e0804a7f57a4a94bc324e553716cee5b14e225c1fdfd2815c2a2128f6e82e3c1036a7f57a4a94bc324e553716cee5b14e225c1fdfd2815c2a4d27a942cdc7c0f014feaf4952978649caa6e2d9dcb629c44b83fbf408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:54:27 GMT" + }, + { + "server": "nginx/1.0.10" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "transfer-encoding": "chunked" + }, + { + "last-modified": "Thu, 16 Aug 2012 08:37:54 GMT" + }, + { + "cache-control": "max-age=300" + }, + { + "content-encoding": "gzip" + }, + { + "age": "1" + }, + { + "x-via": "1.1 bjgm232:8102 (Cdn Cache Server V2.0), 1.1 stsz70:8105 (Cdn Cache Server V2.0), 1.1 gdyf13:9080 (Cdn Cache Server V2.0)" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 41, + "wire": "886196d07abe941094d444a820044a01ab8015c65953168dffc55f87352398ac4c697f0f0d841099645f6c96d07abe94136a435d8a08010a8105c68371b754c5a37f588ba47e561cc5804dbe20001ff6c47f04dc0ae1526a44d02e3c1034a7f57a4a94bc324e553716cee5b14e225c1fdfd2815c2a2124f61719b8f040e29fd5e92a52f0c93954dc5b3b96c53889707f7f4a0570a9349ea50b371e0bcd29fd5e92a52f0c93954dc5b3b96c53889707f7c3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Mon, 22 Oct 2012 04:02:33 GMT" + }, + { + "server": "nginx/1.0.10" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "22332" + }, + { + "last-modified": "Mon, 25 Apr 2011 10:41:57 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "1" + }, + { + "x-via": "1.1 gm240:8104 (Cdn Cache Server V2.0), 1.1 stcz163:8106 (Cdn Cache Server V2.0), 1.1 gdyf13:8184 (Cdn Cache Server V2.0)" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 42, + "wire": "886196c361be94138a6a2254100225022b8d3f7190a98b46ffcac20f0d837d903f6c96d07abe94136a435d8a08010a807ae01db8d894c5a37fc1f9c77f01dc0ae1526a44d3371e081c53fabd254a5e19272a9b8b6772d8a7112e0fefe940ae1510947b75bb8f040d29fd5e92a52f0c93954dc5b3b96c53889707f7f4a0570a9349ea50b971f03c053fabd254a5e19272a9b8b6772d8a7112e0feffc6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Fri, 26 Oct 2012 12:49:31 GMT" + }, + { + "server": "nginx/1.0.10" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "9309" + }, + { + "last-modified": "Mon, 25 Apr 2011 08:07:52 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "1" + }, + { + "x-via": "1.1 gm243:8106 (Cdn Cache Server V2.0), 1.1 stsz75:8104 (Cdn Cache Server V2.0), 1.1 gdyf16:9080 (Cdn Cache Server V2.0)" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 43, + "wire": "887688aa6355e580ae25c16196dc34fd280654d27eea0801128115c6dcb8d3ea62d1bfded2c8d5ccc4f3", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx/1.2.0" + }, + { + "date": "Sat, 03 Nov 2012 12:56:49 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Thu, 16 Aug 2012 08:37:54 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 44, + "wire": "88c2cec60f0d841321719f6c96d07abe94136a435d8a08010a8105c69cb810a98b46ffc552848fd24a8fcc7f03dc0ae1526a44d02e3c10054feaf4952978649caa6e2d9dcb629c44b83fbfa502b8544251edd6ae3c0780a7f57a4a94bc324e553716cee5b14e225c1fdfd2815c2a4d27a942edc782f34a7f57a4a94bc324e553716cee5b14e225c1fdffcb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Fri, 26 Oct 2012 12:49:31 GMT" + }, + { + "server": "nginx/1.0.10" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "23163" + }, + { + "last-modified": "Mon, 25 Apr 2011 10:46:11 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "1" + }, + { + "x-via": "1.1 gm240:8101 (Cdn Cache Server V2.0), 1.1 stsz74:8080 (Cdn Cache Server V2.0), 1.1 gdyf17:8184 (Cdn Cache Server V2.0)" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 45, + "wire": "886196c361be94138a6a2254100225022b8d3f704153168dffd2ca0f0d8213c1c5c8c0ce7f00da0ae1526a44cbf71e0804a7f57a4a94bc324e553716cee5b14e225c1fdfd2815c2a2128f6e82e3cf29fd5e92a52f0c93954dc5b3b96c53889707f7f4a0570a9349ea5102e3c179a53fabd254a5e19272a9b8b6772d8a7112e0fefcd", + "headers": [ + { + ":status": "200" + }, + { + "date": "Fri, 26 Oct 2012 12:49:21 GMT" + }, + { + "server": "nginx/1.0.10" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "281" + }, + { + "last-modified": "Mon, 25 Apr 2011 08:07:52 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "1" + }, + { + "x-via": "1.1 gm239:8102 (Cdn Cache Server V2.0), 1.1 stsz70:88 (Cdn Cache Server V2.0), 1.1 gdyf20:8184 (Cdn Cache Server V2.0)" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 46, + "wire": "886196df3dbf4a002a693f750400894035704fdc038a62d1bfd4cc0f0d033138336c96e4593e940854ca3a9410022500edc6c1719794c5a37fcbc3d17f01fa0ae1513d1330400b8f014feaf4952978649caa6e2d9dcb629c44b83fbfa502b8549a91340b8f040e29fd5e92a52f0c93954dc5b3b96c53889707f7f4a0570a884a3dbaddc7820754feaf4952978649caa6e2d9dcb629c44b83fbfa502b8549a4f5285eb8f32e014feaf4952978649caa6e2d9dcb629c44b83fbfd0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Thu, 01 Nov 2012 04:29:06 GMT" + }, + { + "server": "nginx/1.0.10" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "183" + }, + { + "last-modified": "Wed, 11 Jan 2012 07:50:38 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "1" + }, + { + "x-via": "1.1 tjtg100:80 (Cdn Cache Server V2.0), 1.1 gm240:8106 (Cdn Cache Server V2.0), 1.1 stsz75:8107 (Cdn Cache Server V2.0), 1.1 gdyf18:8360 (Cdn Cache Server V2.0)" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 47, + "wire": "88e86196dc34fd280654d27eea0801128115c6dcb8d814c5a37fcf6c96dd6d5f4a09a521aec5040085400ae04171a1298b46ffdcd26496dc34fd280654d27eea0801128166e09cb8d814c5a37fe60f28c4348dbd001b2e9c145ee38723e47b1cbd42e70d10cd041f6a17cd66b0a8837da5fa50015b49fbac2128115c6dcb8d3aa62d1bfed490f48cd540dbcb90f4fda958d33c0c7fe55a839bd9ab0f0d023335c8", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx/1.0.0" + }, + { + "date": "Sat, 03 Nov 2012 12:56:50 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Sun, 24 Apr 2011 02:10:42 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "expires": "Sat, 03 Nov 2012 13:26:50 GMT" + }, + { + "cache-control": "max-age=1800" + }, + { + "set-cookie": "id58=05eNElCVFI9c8Hfk16UMAg==; expires=Tue, 01-Nov-22 12:56:47 GMT; domain=58.com; path=/" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"CUR ADM OUR NOR STA NID\"" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "35" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 48, + "wire": "887688aa6355e580ae25d9c2d30f0d0233356c96df697e940b8a6a225410022500cdc645700f298b46ffd6ca", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx/1.2.3" + }, + { + "date": "Sat, 03 Nov 2012 12:56:50 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "35" + }, + { + "last-modified": "Tue, 16 Oct 2012 03:32:08 GMT" + }, + { + "connection": "keep-alive" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 49, + "wire": "886196df3dbf4a05e535112a0801128066e05db826d4c5a37fddd50f0d84085b69bf6c96d07abe94136a435d8a08010a807ee01eb8cb8a62d1bfd4ccda7f07dc0ae1526a44d02e3c103aa7f57a4a94bc324e553716cee5b14e225c1fdfd2815c2a2124f616deb8f040d29fd5e92a52f0c93954dc5b3b96c53889707f7f4a0570a9349ea50b971e0bcd29fd5e92a52f0c93954dc5b3b96c53889707f7d9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Thu, 18 Oct 2012 03:17:25 GMT" + }, + { + "server": "nginx/1.0.10" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "11545" + }, + { + "last-modified": "Mon, 25 Apr 2011 09:08:36 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "1" + }, + { + "x-via": "1.1 gm240:8107 (Cdn Cache Server V2.0), 1.1 stcz158:8104 (Cdn Cache Server V2.0), 1.1 gdyf16:8184 (Cdn Cache Server V2.0)" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 50, + "wire": "488264026196dc34fd280654d27eea0801128115c6dcb8d854c5a37f5f87497ca589d34d1fe67f1d88cc52d6b4341bb97f0f28cddf9305d87864bf0123132418ca1682c806091979a1b6eca1fb50be6b3585441be7b7e94642b5f291610040502ddc6dfb8dbea62d1bfed4ac699e063ed490f48cd540931631af18cd25ab90f4ff0f1f989d29aee30c24c58c6bc633496ae43d2c1aa90be579d34d1f40864d832148790b9365a085b71f65c7c2175d79965d65e0840c881f768586b19272ff", + "headers": [ + { + ":status": "302" + }, + { + "date": "Sat, 03 Nov 2012 12:56:51 GMT" + }, + { + "content-type": "text/html" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "Keep-Alive" + }, + { + "set-cookie": "TIEBAUID=cb23caae14130a0d384a57f1; expires=Thu, 31-Dec-2020 15:59:59 GMT; path=/; domain=tieba.baidu.com" + }, + { + "location": "http://tieba.baidu.com/index.html" + }, + { + "tracecode": "34115693691177833738110320" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 51, + "wire": "887689aa6355e580ae05c2df6196dc34fd280654d27eea0801128115c6ddb82694c5a37fecebe14085aec1cd48ff86d27588324e5f5886a8eb10649cbf6496df3dbf4a002a651d4a05f740a0017000b800298b46ff0f28a7cbbb06edd93569c97e01342038d34e3616aeb3780204379b03e17eebecb206c0cfda9ac699e063cef1d80f1391e4c7f2d09e7160b2cbac89d7de740007f36c96c361be940bca681fa5040089403b71b7ee34ea98b46f0f0d83684f39", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx/1.0.15" + }, + { + "date": "Sat, 03 Nov 2012 12:57:24 GMT" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "pragma": "No-cache" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "set-cookie": "JSESSIONID=24206446514B3C020AC50919B9330503; Path=/" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "W/\"4286-1337327987000\"" + }, + { + "last-modified": "Fri, 18 May 2012 07:59:47 GMT" + }, + { + "content-length": "4286" + } + ] + }, + { + "seqno": 52, + "wire": "887688cbbb58980ae05c5f6196dc34fd280654d27eea0801128115c6dcb8d894c5a37f5f88352398ac74acb37fe80f0d836d96c5588ca47e561cc58190b6cb80003f0f1391fe5979b680db4cba169b13afb4fb2cff3f6496c361be940054d27eea080165403b71a15c65c53168df6c96df3dbf4a002a693f75040089403b702f5c69a53168df", + "headers": [ + { + ":status": "200" + }, + { + "server": "JSP2/1.0.2" + }, + { + "date": "Sat, 03 Nov 2012 12:56:52 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "5352" + }, + { + "cache-control": "max-age=31536000" + }, + { + "etag": "\"3854054371452794933\"" + }, + { + "expires": "Fri, 01 Nov 2013 07:42:36 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 07:18:44 GMT" + } + ] + }, + { + "seqno": 53, + "wire": "88c2c1cc0f0d84101b6ddf6c96c361be940094d27eea080112800dc6dfb800298b46ff6496d07abe94032a5f2914100225022b8db971b1298b46ffe9e1cc", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:52 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "20557" + }, + { + "last-modified": "Fri, 02 Nov 2012 01:59:00 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:52 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 54, + "wire": "88eb0f138afe42d3ee09a138d37fcfe16c96dd6d5f4a05b521b66504008140b97000b800298b46ff6496d07abe940894dc5ad4100425022b8db971b1298b46ff588ca47e561cc58190840d0000070f0d830b6eb5c77686bbcb73015c1f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "etag": "\"1496242645\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Sun, 15 Aug 2010 16:00:00 GMT" + }, + { + "expires": "Mon, 12 Sep 2022 12:56:52 GMT" + }, + { + "cache-control": "max-age=311040000" + }, + { + "content-length": "1574" + }, + { + "date": "Sat, 03 Nov 2012 12:56:52 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 55, + "wire": "88c8c7d20f0d840b2e34d76c96dc34fd280654d27eea0801128066e32cdc6c2a62d1bfc3eee6d1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:52 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "13644" + }, + { + "last-modified": "Sat, 03 Nov 2012 03:33:51 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:52 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 56, + "wire": "88c9c8d30f0d837822176c96e4593e94642a6a225410022500e5c69fb8d814c5a37fc4efe7d2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:52 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "8122" + }, + { + "last-modified": "Wed, 31 Oct 2012 06:49:50 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:52 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 57, + "wire": "88d6d5798624f6d5d4b27fd57b8b84842d695b05443c86aa6f6c96dc34fd280654d27eea080112810dc13d704053168dffd5e0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:51 GMT" + }, + { + "content-type": "text/html" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "Keep-Alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Sat, 03 Nov 2012 11:28:20 GMT" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 58, + "wire": "88cdccd70f0d840befb6e76c96dc34fd280654d27eea0801128072e32e5c138a62d1bfc8f3ebd6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:52 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "19956" + }, + { + "last-modified": "Sat, 03 Nov 2012 06:36:26 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:52 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 59, + "wire": "886196dc34fd280654d27eea0801128115c6dcb8db2a62d1bfcec2d9c16c96e4593e94642a6a225410022500edc1337191298b46ffd8e30f0d83684d036496d07abe94032a5f2914100225022b8db971b654c5a37f588ba47e561cc5804dbe20001fef", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "Keep-Alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Wed, 31 Oct 2012 07:23:32 GMT" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "4240" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:53 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 60, + "wire": "885f87352398ac4c697f0f138afe5a71a69a13aebffcfff0cc6496d07abe940894dc5ad4100425022b8db971b654c5a37fcb0f0d821321c3ca", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "etag": "\"464442779\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Sun, 15 Aug 2010 16:00:00 GMT" + }, + { + "expires": "Mon, 12 Sep 2022 12:56:53 GMT" + }, + { + "cache-control": "max-age=311040000" + }, + { + "content-length": "231" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 61, + "wire": "88bf0f1389fe5b642db6171a0ff3f1cdbecb0f0d03333339c3ca", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "etag": "\"531551641\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Sun, 15 Aug 2010 16:00:00 GMT" + }, + { + "expires": "Mon, 12 Sep 2022 12:56:53 GMT" + }, + { + "cache-control": "max-age=311040000" + }, + { + "content-length": "339" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 62, + "wire": "88c3768fc17b568521ac649caa05702e165707e8c8e0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "server": "ECOM Apache 1.0.13.0" + }, + { + "content-encoding": "gzip" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-type": "text/html" + } + ] + }, + { + "seqno": 63, + "wire": "88c4d4df0f0d8375d69e6c96c361be940094d27eea0801128066e00571b7d4c5a37fc3c2f3de", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "7748" + }, + { + "last-modified": "Fri, 02 Nov 2012 03:02:59 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:53 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 64, + "wire": "88d6d5c9e00f28cddf9305d87864bf0123132418ca1682c806091979a1b6eca1fb50be6b3585441be7b7e94642b5f291610040502ddc6dfb8dbea62d1bfed4ac699e063ed490f48cd540931631af18cd25ab90f4ff0f1f989d29aee30c24c58c6bc633496ae43d2c1aa90be579d34d1fdfde0f0d8413ed3ae76c96dc34fd280654d27eea0801128066e321b8d014c5a37fd1c3f4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:52 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "Keep-Alive" + }, + { + "set-cookie": "TIEBAUID=cb23caae14130a0d384a57f1; expires=Thu, 31-Dec-2020 15:59:59 GMT; path=/; domain=tieba.baidu.com" + }, + { + "location": "http://tieba.baidu.com/index.html" + }, + { + "tracecode": "34115693691177833738110320" + }, + { + "server": "Apache" + }, + { + "content-length": "29476" + }, + { + "last-modified": "Sat, 03 Nov 2012 03:31:40 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:52 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 65, + "wire": "88c6d6cae1c96c96df3dbf4a002a693f7504008940377041b806d4c5a37fe0eb0f0d83759007c5c4f5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "Keep-Alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Thu, 01 Nov 2012 05:21:05 GMT" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "7301" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:53 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 66, + "wire": "88c7d7e20f0d8375f7d96c96df3dbf4a002a693f75040089403b702d5c0bea62d1bfc6c5f6e1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "7993" + }, + { + "last-modified": "Thu, 01 Nov 2012 07:14:19 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:53 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 67, + "wire": "88c8c40f0d0234336c96df3dbf4a05a535112a0801028105c082e34da98b46ff7f2588ea52d6b0e83772ffe352848fd24a8f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "last-modified": "Thu, 14 Oct 2010 10:10:45 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 68, + "wire": "88cb5f911d75d0620d263d4c795ba0fb8d04b0d5a76c96c361be9413ea6a225410020500e5c64171b714c5a37fd1c1d0e6cbcaf1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Fri, 29 Oct 2010 06:30:56 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:53 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 69, + "wire": "88cdbf6c96e4593e940b4a6e2d6a08010a8072e059b801298b46ffd2c2d1e7cccbf2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Wed, 14 Sep 2011 06:13:02 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:53 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 70, + "wire": "88dfdee90f0d8465b719776c96e4593e94642a6a225410022500d5c10ae34053168dffdaccc2e8", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:52 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "35637" + }, + { + "last-modified": "Wed, 31 Oct 2012 04:22:40 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:52 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 71, + "wire": "88cfdfd3ea0f28cddf9305d87864bf0123132418ca1682c806091979a1b6eca1fb50be6b3585441be7b7e94642b5f291610040502ddc6dfb8dbea62d1bfed4ac699e063ed490f48cd540931631af18cd25ab90f4ff0f1f989d29aee30c24c58c6bc633496ae43d2c1aa90be579d34d1fe9e80f0d8369f7dd6c96df3dbf4a082a65b6a5040089403371b76e32ea98b46fcecdc3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "Keep-Alive" + }, + { + "set-cookie": "TIEBAUID=cb23caae14130a0d384a57f1; expires=Thu, 31-Dec-2020 15:59:59 GMT; path=/; domain=tieba.baidu.com" + }, + { + "location": "http://tieba.baidu.com/index.html" + }, + { + "tracecode": "34115693691177833738110320" + }, + { + "server": "Apache" + }, + { + "content-length": "4997" + }, + { + "last-modified": "Thu, 21 Jun 2012 03:57:37 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:53 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 72, + "wire": "88d0e0d4ebd36c96c361be9413ea65b6a50400894082e059b81714c5a37feaf50f0d83109c77cfcec4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "Keep-Alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Fri, 29 Jun 2012 10:13:16 GMT" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "2267" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:53 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 73, + "wire": "88d1e1ec0f0d830b4f036c96d07abe941054d03f4a0801128076e36f5c038a62d1bfd0cfc5eb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "1480" + }, + { + "last-modified": "Mon, 21 May 2012 07:58:06 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:53 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 74, + "wire": "88d2e2ed0f0d8379f10b6c96d07abe941094d444a820044a019b820dc03aa62d1bffd1d0c6ec", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "8922" + }, + { + "last-modified": "Mon, 22 Oct 2012 03:21:07 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:53 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 75, + "wire": "88e30f138afe5a0ba20bad81907f3fc66c96df697e94132a6a2254100225020b8d3d704ea98b46ff0f0d837d97d9d476841d6324e5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "etag": "\"4172175030\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 23 Oct 2012 10:48:27 GMT" + }, + { + "content-length": "9393" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "server": "apache" + } + ] + }, + { + "seqno": 76, + "wire": "88d55f87352398ac5754dff10f0d837c4fb56c96df697e94640a6a225410022500f5c0b9704e298b46ffd5d4caf0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "image/png" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "9294" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:16:26 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:53 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 77, + "wire": "88e8e7f20f0d8468027dff6c96c361be940094d27eea080112806ae01db800298b46ffe3d5cbf1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:52 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "40299" + }, + { + "last-modified": "Fri, 02 Nov 2012 04:07:00 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:52 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 78, + "wire": "886196dc34fd280654d27eea0801128115c6dcb8db4a62d1bfc1ddf4dc6c96c361be940094d27eea080112807ae32e5c69a53168dff35a839bd9ab0f0d8379b75f6496d07abe94032a5f2914100225022b8db971b694c5a37fd9cf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:54 GMT" + }, + { + "content-type": "image/png" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "Keep-Alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:36:44 GMT" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "8579" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:54 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 79, + "wire": "88dcc4f70f0d840bce01bf6c96c361be940094d27eea080112807ae32e5c69953168dfbfdad0f6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "image/png" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "18605" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:36:43 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:54 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 80, + "wire": "88c2c5e1f80f28cddf9305d87864bf0123132418ca1682c806091979a1b6eca1fb50be6b3585441be7b7e94642b5f291610040502ddc6dfb8dbea62d1bfed4ac699e063ed490f48cd540931631af18cd25ab90f4ff0f1f989d29aee30c24c58c6bc633496ae43d2c1aa90be579d34d1ff7f60f0d841000fbffbebfdad0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:54 GMT" + }, + { + "content-type": "image/png" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "Keep-Alive" + }, + { + "set-cookie": "TIEBAUID=cb23caae14130a0d384a57f1; expires=Thu, 31-Dec-2020 15:59:59 GMT; path=/; domain=tieba.baidu.com" + }, + { + "location": "http://tieba.baidu.com/index.html" + }, + { + "tracecode": "34115693691177833738110320" + }, + { + "server": "Apache" + }, + { + "content-length": "20099" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:36:43 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:54 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 81, + "wire": "88c2c5f80f0d840bcf38dfc1bfdad0f6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:54 GMT" + }, + { + "content-type": "image/png" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "18865" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:36:44 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:54 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 82, + "wire": "88c2c5f80f0d841002267fc1bfdad0f6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:54 GMT" + }, + { + "content-type": "image/png" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "20123" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:36:44 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:54 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 83, + "wire": "88dd5f86497ca582211f6c96c361be940094d27eea080112807ae321b81694c5a37fe3d3e2f8c2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "text/css" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:31:14 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 84, + "wire": "48826402c5f90f1fa79d29aee30c5289978c6692d5c87a58a513314a268a41a4788a9a5135e3db527fc96c3d305289bfe3c30f0d82101c7f15842507417f5f95497ca589d34d1f6a1271d882a60320eb3cf36fac1f", + "headers": [ + { + ":status": "302" + }, + { + "date": "Sat, 03 Nov 2012 12:56:54 GMT" + }, + { + "server": "Apache" + }, + { + "location": "http://msg.baidu.com/msg/msg_dataGetmsgCount?from=msg" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "206" + }, + { + "connection": "close" + }, + { + "content-type": "text/html; charset=iso-8859-1" + } + ] + }, + { + "seqno": 85, + "wire": "88c7f27f0088cc52d6b4341bb97f0f0d840baf38ffc4c5e0d6fc", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:54 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "17869" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:36:43 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:54 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 86, + "wire": "88c8cbbe0f0d840bc0745fc7c5e0d6fc", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:54 GMT" + }, + { + "content-type": "image/png" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "18072" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:36:44 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:54 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 87, + "wire": "88e3f30f0d84105913df6c96e4593e940bea651d4a08010a807ee05cb810a98b46ffd8768586b19272ffe3e2d8", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "21328" + }, + { + "last-modified": "Wed, 19 Jan 2011 09:16:11 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:53 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 88, + "wire": "88cae1c00f0d830b4f076c96df697e94134a65b685040089403371b7ee09953168dfc8e3d9bf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:54 GMT" + }, + { + "content-type": "image/gif" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "1481" + }, + { + "last-modified": "Tue, 24 Jul 2012 03:59:23 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:54 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 89, + "wire": "88cbbfe9c90f0d821081c35f87497ca589d34d1f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:54 GMT" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "220" + }, + { + "connection": "close" + }, + { + "content-type": "text/html" + } + ] + }, + { + "seqno": 90, + "wire": "88f70f138afe597dc6da03c1683fcfda6c96df697e940b8a6a225410022500e5c6dab8dbca62d1bff1f00f0d8465f7df67f9ef", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "etag": "\"3965408141\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 16 Oct 2012 06:54:58 GMT" + }, + { + "expires": "Mon, 12 Sep 2022 12:56:52 GMT" + }, + { + "cache-control": "max-age=311040000" + }, + { + "content-length": "39993" + }, + { + "date": "Sat, 03 Nov 2012 12:56:52 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 91, + "wire": "88cdd0c30f0d840844ebffc9cae5dbc1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:54 GMT" + }, + { + "content-type": "image/png" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "11279" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:36:43 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:54 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 92, + "wire": "88cdd0ecc3ebccc1cb0f0d84109979afcae5db", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:54 GMT" + }, + { + "content-type": "image/png" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "Keep-Alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:36:44 GMT" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "22384" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:54 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 93, + "wire": "88cdd0ecc30f28cddf9305d87864bf0123132418ca1682c806091979a1b6eca1fb50be6b3585441be7b7e94642b5f291610040502ddc6dfb8dbea62d1bfed4ac699e063ed490f48cd540931631af18cd25ab90f4ff0f1f989d29aee30c24c58c6bc633496ae43d2c1aa90be579d34d1f40864d832148790b9365a085b71f65c7c2175d79965d65e0840c881fc20f0d84101f705fcacbe6dc", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:54 GMT" + }, + { + "content-type": "image/png" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "Keep-Alive" + }, + { + "set-cookie": "TIEBAUID=cb23caae14130a0d384a57f1; expires=Thu, 31-Dec-2020 15:59:59 GMT; path=/; domain=tieba.baidu.com" + }, + { + "location": "http://tieba.baidu.com/index.html" + }, + { + "tracecode": "34115693691177833738110320" + }, + { + "server": "Apache" + }, + { + "content-length": "20962" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:36:43 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:54 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 94, + "wire": "88e9f9c40f0d84702d34d76c96e4593e94642a6a225410022500edc0b3702e298b46ffe8e7ddc3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "61444" + }, + { + "last-modified": "Wed, 31 Oct 2012 07:13:16 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:53 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 95, + "wire": "88cfd2c50f0d840baeb22f6c96c361be940094d27eea080112807ee05cb82654c5a37fcde8dec4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:54 GMT" + }, + { + "content-type": "image/png" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "17732" + }, + { + "last-modified": "Fri, 02 Nov 2012 09:16:23 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:54 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 96, + "wire": "88ebdd6c96c361be94138a6a2254100225022b8dbd702ca98b46fff0e0efc5cf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Fri, 26 Oct 2012 12:58:13 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 97, + "wire": "88ecdecbf0e0efc5eae9cf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:53 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:31:14 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:53 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 98, + "wire": "886196dc34fd280654d27eea0801128115c6dcb8dbaa62d1bfe9f1c8f06c96df697e940854d444a820042a01db8cbd700fa98b46ffc7d10f0d033939396496d07abe94032a5f2914100225022b8db971b754c5a37fece2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:57 GMT" + }, + { + "content-type": "image/gif" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "Keep-Alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Tue, 11 Oct 2011 07:38:09 GMT" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "999" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:57 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 99, + "wire": "88c0d7ca0f0d8365f69f6c96d07abe9413aa436cca0801128072e32e5c6c0a62d1bfbfede3c9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:57 GMT" + }, + { + "content-type": "image/png" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "3949" + }, + { + "last-modified": "Mon, 27 Aug 2012 06:36:50 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:57 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 100, + "wire": "88c1d8cb0f0d83085b7b6c96d07abe94032a6e2d6a0801128066e34edc6dd53168dfc0eee4ca", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:57 GMT" + }, + { + "content-type": "image/png" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "1158" + }, + { + "last-modified": "Mon, 03 Sep 2012 03:47:57 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:57 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 101, + "wire": "88c2d9cc0f0d830800f76c96d07abe9413aa436cca080112807ae09cb81754c5a37fc1efe5cb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:57 GMT" + }, + { + "content-type": "image/png" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "1008" + }, + { + "last-modified": "Mon, 27 Aug 2012 08:26:17 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:57 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 102, + "wire": "88c3da6c96e4593e940b4a6e2d6a08010a8072e059b80714c5a37ff7e7f6ccc2f0d60f0d83640dbfe6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:57 GMT" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Wed, 14 Sep 2011 06:13:06 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:57 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "3059" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 103, + "wire": "88c45f91497ca589d34d1f6a1271d882a60c57737ff8cf0f28d2df9305d862e1bb06ddfcf5e081d1886e3a1189c14616e374ae4ad3c17e3fb50be6b3585441be7b7e94642b5f291610040502ddc6dfb8dbea62d1bfed4ac699e063ed490f48cd540931631af18cd25ab90f4f0f1f989d29aee30c24c58c6bc633496ae43d2c1aa90be579d34d1f7f0a9365a0bacbce899640cbcf32e884cb410819103fce0f0d84101f705fd6d7f2e8f8d8", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:57 GMT" + }, + { + "content-type": "text/html; charset=GBK" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "Keep-Alive" + }, + { + "set-cookie": "TIEBA_USERTYPE=7a2a671a262b15b7e6f4819b; expires=Thu, 31-Dec-2020 15:59:59 GMT; path=/; domain=tieba.baidu.com" + }, + { + "location": "http://tieba.baidu.com/index.html" + }, + { + "tracecode": "34173872330388372234110320" + }, + { + "server": "Apache" + }, + { + "content-length": "20962" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:36:43 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:54 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 104, + "wire": "88c6bfd00f0d83085b7bc2c4f2e8cef9f87e9365a0bae3c07df740d3af05b75910821032207fd9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:57 GMT" + }, + { + "content-type": "text/html; charset=GBK" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "1158" + }, + { + "last-modified": "Mon, 03 Sep 2012 03:47:57 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:57 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + }, + { + "transfer-encoding": "chunked" + }, + { + "vary": "Accept-Encoding" + }, + { + "tracecode": "34176809970478157322110320" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 105, + "wire": "88c7de0f0d03363037ceeacfc5f3e9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:57 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "607" + }, + { + "last-modified": "Tue, 24 Jul 2012 03:59:23 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:57 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 106, + "wire": "88c7de0f0d8313e20f6c96df697e940baa681fa504008540397197ee004a62d1bfebd0c6f4ea", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:57 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2921" + }, + { + "last-modified": "Tue, 17 May 2011 06:39:02 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:57 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 107, + "wire": "88c8df0f0d8313cc8bbeebd0c6f4ea", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:57 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2832" + }, + { + "last-modified": "Tue, 17 May 2011 06:39:02 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:57 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 108, + "wire": "88c8df0f0d83640dbfbeebd0c6f4ea", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:57 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "3059" + }, + { + "last-modified": "Tue, 17 May 2011 06:39:02 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:57 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 109, + "wire": "88c8df0f0d8313c00fbeebd0c6f4ea", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:57 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2801" + }, + { + "last-modified": "Tue, 17 May 2011 06:39:02 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:57 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 110, + "wire": "88c8df0f0d8313e267c2ebd0c6f4ea", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:57 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2923" + }, + { + "last-modified": "Wed, 14 Sep 2011 06:13:06 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:57 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 111, + "wire": "88c8dfd20f0d841004207f6c96df697e94640a6a225410022500edc6dcb806d4c5a37fc7f5ebd1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:57 GMT" + }, + { + "content-type": "image/png" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "20220" + }, + { + "last-modified": "Tue, 30 Oct 2012 07:56:05 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:57 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 112, + "wire": "886196dc34fd280654d27eea0801128115c6dcb8dbca62d1bfe10f0d8313cc83c0edd26496d07abe94032a5f2914100225022b8db971b794c5a37ff7ed", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:58 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2830" + }, + { + "last-modified": "Tue, 17 May 2011 06:39:02 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:58 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 113, + "wire": "88bfe20f0d8313cebbc1eed3bef7ed", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:58 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2877" + }, + { + "last-modified": "Tue, 17 May 2011 06:39:02 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:58 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 114, + "wire": "88bfe20f0d8313ee3bc5eed3bef7ed", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:58 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2967" + }, + { + "last-modified": "Wed, 14 Sep 2011 06:13:06 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:58 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 115, + "wire": "88bff60f0d03313737c5eed3bef7ed", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:58 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "177" + }, + { + "last-modified": "Wed, 14 Sep 2011 06:13:06 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:58 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 116, + "wire": "88bfc4d50f0d83085b7bc7c9f7edd3798624f6d5d4b27f7b8b84842d695b05443c86aa6f7f059265a0bc00be26df034cbce81979e104206440e0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:58 GMT" + }, + { + "content-type": "text/html; charset=GBK" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "1158" + }, + { + "last-modified": "Mon, 03 Sep 2012 03:47:57 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:57 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + }, + { + "transfer-encoding": "chunked" + }, + { + "vary": "Accept-Encoding" + }, + { + "tracecode": "34180192590438703882110320" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 117, + "wire": "88c2f90f0d83740e3bdcf1d6c1faf0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:58 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "7067" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:31:14 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:58 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 118, + "wire": "88c2e56c96e4593e940b4a6e2d6a08010a8072e04571b0a98b46ffc1f2c0d7c2588ba47e561cc5804dbe20001fe20f0d03323838f2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:58 GMT" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Wed, 14 Sep 2011 06:12:51 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:58 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "288" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 119, + "wire": "88c45f87352398ac4c697f0f0d83784cb9cbf4d9c4bff3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:58 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "8236" + }, + { + "last-modified": "Wed, 14 Sep 2011 06:13:06 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:58 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 120, + "wire": "886196dc34fd280654d27eea0801128115c6dcb8dbea62d1bfe90f0d8313cd0bc8f5da6496d07abe94032a5f2914100225022b8db971b7d4c5a37fc1f5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2842" + }, + { + "last-modified": "Tue, 17 May 2011 06:39:02 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:59 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 121, + "wire": "885f8b497ca58e83ee3412c3569f0f138afe5a13e1684d3adbbfcff66c96df3dbf4a082a65b6a5040089403d700d5c69c53168df6496d07abe940894dc5ad4100425022b8db971b7d4c5a37f588ca47e561cc58190840d000007c8e90f0d8365b6dec37686bbcb73015c1f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/javascript" + }, + { + "etag": "\"4291424757\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Thu, 21 Jun 2012 08:04:46 GMT" + }, + { + "expires": "Mon, 12 Sep 2022 12:56:59 GMT" + }, + { + "cache-control": "max-age=311040000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "3558" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 122, + "wire": "88de0f138afe5a132271f682fbdfcffa6c96df3dbf4a082a65b6a5040089403d700d5c69b53168dfc1c0caeb0f0d84081f007fc5bf", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/html" + }, + { + "etag": "\"4232694198\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Thu, 21 Jun 2012 08:04:45 GMT" + }, + { + "expires": "Mon, 12 Sep 2022 12:56:59 GMT" + }, + { + "cache-control": "max-age=311040000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "10901" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 123, + "wire": "88c5768b1d6324e5502b857138b83f5f88352398ac74acb37f588ca47e561cc581a69e69f65f7f6496dd6d5f4a01c521aec50400b4a05bb8072e36f298b46f6c96df3dbf4a32152f948a08007d403d71976e002a62d1bfd0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=44849399" + }, + { + "expires": "Sun, 06 Apr 2014 15:06:58 GMT" + }, + { + "last-modified": "Thu, 31 Dec 2009 08:37:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 124, + "wire": "88cac2c1588ca47e561cc581a009e001e6bf6496e4593e940894c258d41002d28176e361b8d32a62d1bf6c96c361be940b8a435d8a0801028066e01db8c854c5a37fd3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=40280084" + }, + { + "expires": "Wed, 12 Feb 2014 17:51:43 GMT" + }, + { + "last-modified": "Fri, 16 Apr 2010 03:07:31 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 125, + "wire": "88cdc5c4588ca47e561cc58022742cb8267f6496dc34fd28c814d03b141002ca8172e320b8d094c5a37f6c96dc34fd281694ca3a9410022500ddc69fb8cb2a62d1bfd6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=12713623" + }, + { + "expires": "Sat, 30 Mar 2013 16:30:42 GMT" + }, + { + "last-modified": "Sat, 14 Jan 2012 05:49:33 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 126, + "wire": "88d0c8c7588ca47e561cc581965b65f79c176496df697e94138a693f750400b2a05db8cb571a0a98b46f6c96dd6d5f4a05f53716b5040081403371a0dc65b53168dfd9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=33539862" + }, + { + "expires": "Tue, 26 Nov 2013 17:34:41 GMT" + }, + { + "last-modified": "Sun, 19 Sep 2010 03:41:35 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 127, + "wire": "88d3cbca588ba47e561cc581a680d85f6f6496d07abe94134a5f2914100225022b8cb971b694c5a37f6c96df697e94134a65b68504008940b371976e01f53168dfdc", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4405195" + }, + { + "expires": "Mon, 24 Dec 2012 12:36:54 GMT" + }, + { + "last-modified": "Tue, 24 Jul 2012 13:37:09 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 128, + "wire": "884084a4b2187f84a4b2187f588ca47e561cc58190b6cb80003f6496c361be94138a6a2254100225022b826ae05953168dff6c96dc34fd2826d486bb141000fa8076e01ab800298b46ffd1e276841d6324e50f0d8413a16dff", + "headers": [ + { + ":status": "200" + }, + { + "media": "media" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Fri, 26 Oct 2012 12:24:13 GMT" + }, + { + "last-modified": "Sat, 25 Apr 2009 07:04:00 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "date": "Sat, 03 Nov 2012 12:56:58 GMT" + }, + { + "server": "apache" + }, + { + "content-length": "27159" + } + ] + }, + { + "seqno": 129, + "wire": "88dc0f138afe44271f784f36007f3f52848fd24a8f6c96df697e94134a436cca080102816ae09cb8d054c5a37fd9d8e25a839bd9ab0f0d03353731ded8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "etag": "\"2269828500\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 24 Aug 2010 14:26:41 GMT" + }, + { + "expires": "Mon, 12 Sep 2022 12:56:59 GMT" + }, + { + "cache-control": "max-age=311040000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "571" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 130, + "wire": "88ded6d5588ca47e561cc5804d842175d0ff6496e4593e94105486d9941002ca806ae09cb8c814c5a37f6c96dc34fd2801290d762820042a01bb8dbb71b714c5a37fe7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=25111771" + }, + { + "expires": "Wed, 21 Aug 2013 04:26:30 GMT" + }, + { + "last-modified": "Sat, 02 Apr 2011 05:57:56 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 131, + "wire": "88e1d9d8588ba47e561cc581f13ee8441f6496df697e940bea612c6a0801654033704fdc0014c5a37f6c96d07abe94009486bb1410022500edc6c571b714c5a37fea", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=9297121" + }, + { + "expires": "Tue, 19 Feb 2013 03:29:00 GMT" + }, + { + "last-modified": "Mon, 02 Apr 2012 07:52:56 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 132, + "wire": "88e4dcdb588ca47e561cc581a0bc2001d6ff6496dd6d5f4a004a681d8a08016940b37197ae05a53168df6c96df3dbf4a042a681d8a080102810dc65ab827d4c5a37fed", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=41820075" + }, + { + "expires": "Sun, 02 Mar 2014 13:38:14 GMT" + }, + { + "last-modified": "Thu, 11 Mar 2010 11:34:29 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 133, + "wire": "88e7dfde588ba47e561cc58020101f101a6496c361be940054d03b141002ca8172e360b82654c5a37f6c96d07abe940894d03b1410022500ddc082e040a62d1bfff0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=10209204" + }, + { + "expires": "Fri, 01 Mar 2013 16:50:23 GMT" + }, + { + "last-modified": "Mon, 12 Mar 2012 05:10:10 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 134, + "wire": "880f139728e3746091c8de748cc6d1641104e808065e0491ca27ff5893a47e561cc5801f4a536a12b585ee3a0d20d25fcb5f901d75d0620d263d4c741f71a0961ab4ff0f28c7c7a21bd7b570d3be006179b002fbe17da1061bf77ed4d634cf031f6a5f3d2335504f4af18cd25ab90f4fda983cd66b0a88375b57d281794ca3a941019794002e001700053168df4003703370c7bdae0fe6f70da3521bfa06a5fc1c46a6bdd09d4d7baf9d4d5c36a97786e53869c8a6be1b54c9a77a97f0685376f854d7b70297b568534c3c54d5bef29a756452feed6a5ed5b7f9408721eaa8a4498f57842507417f0f0d836dd75feed1", + "headers": [ + { + ":status": "200" + }, + { + "etag": "eab7a0d6b87c3b4ed2c270c0380dbf29" + }, + { + "cache-control": "max-age=0, must-revalidate" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "application/javascript" + }, + { + "set-cookie": "HMACCOUNT=0F8500D919421ADB; Path=/; Domain=hm.baidu.com; Expires=Sun, 18 Jan 2038 00:00:00 GMT" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "connection": "close" + }, + { + "content-length": "5779" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "server": "apache" + } + ] + }, + { + "seqno": 135, + "wire": "88eee6e5588ba47e561cc581e6c2db4f336496dd6d5f4a040a612c6a080165400ae08371a1298b46ff6c96c361be94101486bb14100225020b8076e32253168dfff7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=8515483" + }, + { + "expires": "Sun, 10 Feb 2013 02:21:42 GMT" + }, + { + "last-modified": "Fri, 20 Apr 2012 10:07:32 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 136, + "wire": "885895aec3771a4bf4a523f2b0e62c00fa52a3ac419272ff4085aec1cd48ff86a8eb10649cbff44090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cbc40f0d0234336196dc34fd280654d27eea0801128115c6ddb800298b46ffd8", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, max-age=0, no-cache" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "x-content-type-options": "nosniff" + }, + { + "connection": "close" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 12:57:00 GMT" + }, + { + "server": "apache" + } + ] + }, + { + "seqno": 137, + "wire": "88be5f911d75d0620d263d4c795ba0fb8d04b0d5a77f0788cc52d6b4341bb97f0f0d83085b7b6c96c361be940094d27eea080112810dc6dab82794c5a37f6496d07abe94032a5f2914100225022b8dbb700053168dfffbdb768586b19272ff798624f6d5d4b27f7b8b84842d695b05443c86aa6f40864d832148790b9265a0bc00be26df034cbce81979e104206440dd", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:00 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "1158" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:54:28 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:00 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + }, + { + "transfer-encoding": "chunked" + }, + { + "vary": "Accept-Encoding" + }, + { + "tracecode": "34180192590438703882110320" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 138, + "wire": "88c6c5c40f0d83085b7bc3c2588ba47e561cc5804dbe20001fe0c2c1c0bfde", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:00 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "1158" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:54:28 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:00 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + }, + { + "transfer-encoding": "chunked" + }, + { + "vary": "Accept-Encoding" + }, + { + "tracecode": "34180192590438703882110320" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 139, + "wire": "88c7c6c50f0d8365f69f6c96c361be940094d27eea0801128115c086e32e298b46ffc4bfe1c3c2c1df", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:00 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "3949" + }, + { + "last-modified": "Fri, 02 Nov 2012 12:11:36 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:00 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + }, + { + "transfer-encoding": "chunked" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 140, + "wire": "880f28e8bb0e4bfc325f81b66e821680e3f089fbb82f5f0b9830b617df79abd7a1001bb9871401fb5291f958731607da700f000007da85f359ac2a20d07abe9413ab6a2256684a04571b76e004a62d1bfed490f48cd540bc633496ae43d3f6a5634cf031f6a772d8831ea8037f119ebdae0fe54d5bf2297f76b52f6adaa64e30a9ab86d53269bea5ed5a14fe7f5f8b497ca58e83ee3412c3569f0f138afe42e3ef38eb2dba2fe7e36c96df697e94640a6a225410022500f5c69fb8dbca62d1bf6496c361be940054c258d41002ca8115c6ddb801298b46ff588ba47e561cc581d75d700007c6e40f0d826c026196dc34fd280654d27eea0801128115c6ddb801298b46ffe8", + "headers": [ + { + ":status": "200" + }, + { + "set-cookie": "BAIDUID=53B0A4069A29BECD16EF519984CCA005:FG=1; max-age=946080000; expires=Mon, 27-Oct-42 12:57:02 GMT; domain=.baidu.com; path=/; version=1" + }, + { + "p3p": "CP=\" OTI DSP COR IVA OUR IND COM \"" + }, + { + "content-type": "text/javascript" + }, + { + "etag": "\"1698673572\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:49:58 GMT" + }, + { + "expires": "Fri, 01 Feb 2013 12:57:02 GMT" + }, + { + "cache-control": "max-age=7776000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "502" + }, + { + "date": "Sat, 03 Nov 2012 12:57:02 GMT" + }, + { + "server": "apache" + } + ] + }, + { + "seqno": 141, + "wire": "880f28e9bb0e4bfc325f81b66e821680e3f089fbb82f5fbe07efde784fdec18216438305cc38a00fda948fcac398b03ed3807800003ed42f9acd61510683d5f4a09d5b5112b3425022b8dbb700253168dff6a487a466aa05e319a4b5721e9fb52b1a67818fb53b96c418f5401fc3c20f138afe44eb8f880dbc007f3fe7c1c0bfc7e50f0d8365d08bbee8", + "headers": [ + { + ":status": "200" + }, + { + "set-cookie": "BAIDUID=53B0A4069A29BECDD09DC829CEEA31EE:FG=1; max-age=946080000; expires=Mon, 27-Oct-42 12:57:02 GMT; domain=.baidu.com; path=/; version=1" + }, + { + "p3p": "CP=\" OTI DSP COR IVA OUR IND COM \"" + }, + { + "content-type": "text/javascript" + }, + { + "etag": "\"2769205800\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:49:58 GMT" + }, + { + "expires": "Fri, 01 Feb 2013 12:57:02 GMT" + }, + { + "cache-control": "max-age=7776000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "3712" + }, + { + "date": "Sat, 03 Nov 2012 12:57:02 GMT" + }, + { + "server": "apache" + } + ] + }, + { + "seqno": 142, + "wire": "885f87352398ac5754df0f138afe42e0401684e3ef7f3fe86c96df3dbf4a082a65b6a5040089403d700d5c69d53168df6496d07abe940894dc5ad4100425022b8db971b7d4c5a37f588ca47e561cc58190840d000007cbe90f0d850b6f3e207f6196dc34fd280654d27eea0801128115c6dcb8dbea62d1bf7686bbcb73015c1f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "etag": "\"1610142698\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Thu, 21 Jun 2012 08:04:47 GMT" + }, + { + "expires": "Mon, 12 Sep 2022 12:56:59 GMT" + }, + { + "cache-control": "max-age=311040000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "158920" + }, + { + "date": "Sat, 03 Nov 2012 12:56:59 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 143, + "wire": "886196dc34fd280654d27eea0801128115c6ddb80694c5a37f5f89352398ac7958c43d5fd40f0d83136d836c96df697e94640a681d8a080102807ee09bb8d814c5a37f6496d07abe94032a5f2914100225022b8dbb700d298b46ffcff1d3d2d1d0ef", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:04 GMT" + }, + { + "content-type": "image/x-icon" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "2550" + }, + { + "last-modified": "Tue, 30 Mar 2010 09:25:50 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:04 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + }, + { + "transfer-encoding": "chunked" + }, + { + "vary": "Accept-Encoding" + }, + { + "tracecode": "34180192590438703882110320" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 144, + "wire": "886196dc34fd280654d27eea0801128115c6ddb80754c5a37f5f87352398ac4c697f6c96e4593e940b4a6e2d6a08010a8072e04371a1298b46ffd57f1a88ea52d6b0e83772ffd5d76496d07abe94032a5f2914100225022b8db971b794c5a37fd4f40f0d023433f6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:07 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Wed, 14 Sep 2011 06:11:42 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:56:58 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "43" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 145, + "wire": "88df5887a47e561cc5801fc20f138afe42f81b79d00421fe7ff76c96e4593e940bca693f7504003ea01fb8d35700fa98b46f6496dc34fd280654d27eea0801128115c6ddb80754c5a37f0f0d0130c5ca", + "headers": [ + { + ":status": "200" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "max-age=0" + }, + { + "content-type": "image/gif" + }, + { + "etag": "\"1905870111\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Wed, 18 Nov 2009 09:44:09 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 12:57:07 GMT" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 12:57:07 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 146, + "wire": "88e3e2c4e1e70f0d023433c5fa", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, max-age=0, no-cache" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "x-content-type-options": "nosniff" + }, + { + "connection": "close" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 12:57:07 GMT" + }, + { + "server": "apache" + } + ] + }, + { + "seqno": 147, + "wire": "886196dc34fd280654d27eea0801128115c6ddb80794c5a37f5f86497ca582211f6c96df697e94640a6a225410022500fdc106e36e298b46ffddc5dcdefa6496d07abe94032a5f2914100225022b8dbb700f298b46ffdb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "text/css" + }, + { + "last-modified": "Tue, 30 Oct 2012 09:21:56 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + } + ] + }, + { + "seqno": 148, + "wire": "88c1c86c96df3dbf4a002a693f7504008940b3704ddc0054c5a37fdfc7dee0bfdcfc0f0d0337383752848fd24a8f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "787" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 149, + "wire": "88c3ca0f0d826c42bfc8e1c0ddbe", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "522" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 150, + "wire": "88c3ce0f0d83136d836c96df697e94134a65b685040089403371b7ee09953168dfc9e2bfc1de", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "image/x-icon" + }, + { + "content-length": "2550" + }, + { + "last-modified": "Tue, 24 Jul 2012 03:59:23 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "accept-ranges": "bytes" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + } + ] + }, + { + "seqno": 151, + "wire": "88c4768b1d6324e5502b857138b83f5f88352398ac74acb37f588ca47e561cc5802d3e203e173f6496df3dbf4a09b521aec50400b2a01bb8cbf700d298b46f6c96df3dbf4a09a5349fba820042a019b8cb3702da98b46fe6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=14920916" + }, + { + "expires": "Thu, 25 Apr 2013 05:39:04 GMT" + }, + { + "last-modified": "Thu, 24 Nov 2011 03:33:15 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 152, + "wire": "88c9c2c1588ca47e561cc5804013a275c67f6496e4593e94138a65b6a50400b2a01ab8172e32153168df6c96dc34fd282654cb6d0a08010a8072e05eb821298b46ffe9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=20272763" + }, + { + "expires": "Wed, 26 Jun 2013 04:16:31 GMT" + }, + { + "last-modified": "Sat, 23 Jul 2011 06:18:22 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 153, + "wire": "88ccc5c4588ba47e561cc581965e71e6996496e4593e940894be522820044a05db8d357190a98b46ff6c96c361be940baa436cca0801128066e085704253168dffec", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3386843" + }, + { + "expires": "Wed, 12 Dec 2012 17:44:31 GMT" + }, + { + "last-modified": "Fri, 17 Aug 2012 03:22:22 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 154, + "wire": "88cfc8c7588aa47e561cc58190bef0036496e4593e9403aa693f75040089403771a76e01f53168df6c96dc34fd282754d444a820044a019b8176e01c53168dffef", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=319801" + }, + { + "expires": "Wed, 07 Nov 2012 05:47:09 GMT" + }, + { + "last-modified": "Sat, 27 Oct 2012 03:17:06 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 155, + "wire": "88e40f138afe44f059038e34f37fcfcd6c96df3dbf4a044a436cca080102807ae34fdc6c4a62d1bf6496d07abe940894dc5ad4100425022b8dbb700f298b46ffe30f0d830b4f83d4e1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "etag": "\"2813066485\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Thu, 12 Aug 2010 08:49:52 GMT" + }, + { + "expires": "Mon, 12 Sep 2022 12:57:08 GMT" + }, + { + "cache-control": "max-age=311040000" + }, + { + "content-length": "1490" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 156, + "wire": "88d4d36c96c361be940094d27eea080112807ae321b81694c5a37ff2daf1f35a839bd9abd3f0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "text/css" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:31:14 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + } + ] + }, + { + "seqno": 157, + "wire": "88d6d56c96c361be940094d27eea080112810dc03f702053168dfff4dcf3f5bfd4f1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "text/css" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + } + ] + }, + { + "seqno": 158, + "wire": "88d7d6c0f4dcf3f5d4f1bf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "text/css" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:31:14 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 159, + "wire": "88d7ded3f4dcf3f5d4f1bf0f0d8369d0b7d2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "4715" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 160, + "wire": "88d7ded3f4dcf3f5d4f1bf0f0d836de69ed2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "5848" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 161, + "wire": "88d7d66c96df3dbf4a09f5340ec5040089403d7040b820298b46fff5ddf4f6d5f2c0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "text/css" + }, + { + "last-modified": "Thu, 29 Mar 2012 08:20:20 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 162, + "wire": "88d8d1d0588ba47e561cc580217590bccf6496dc34fd281754d27eea0801128015c6c1702153168dff6c96dd6d5f4a01d535112a080112807ee043700253168dfff8", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1173183" + }, + { + "expires": "Sat, 17 Nov 2012 02:50:11 GMT" + }, + { + "last-modified": "Sun, 07 Oct 2012 09:11:02 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 163, + "wire": "88dbd4d3588aa47e561cc5802c85c0356496d07abe94036a693f750400894006e320b8c894c5a37f6c96e4593e94642a6a2254100225021b8d82e05f53168dfffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=131604" + }, + { + "expires": "Mon, 05 Nov 2012 01:30:32 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 11:50:19 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 164, + "wire": "88ded7d6588ba47e561cc5819036d3ce7f6496e4593e9403aa693f750400894006e34f5c65a53168df6c96dc34fd282754d444a820044a043702d5c0b8a62d1bff798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=305486" + }, + { + "expires": "Wed, 07 Nov 2012 01:48:34 GMT" + }, + { + "last-modified": "Sat, 27 Oct 2012 11:14:16 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 165, + "wire": "88e2dbda588ca47e561cc58020081c780fff6496df3dbf4a09e53096350400b2a045704cdc6dd53168df6c96e4593e940b4a681d8a080112816ae019b827d4c5a37fc1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=10106809" + }, + { + "expires": "Thu, 28 Feb 2013 12:23:57 GMT" + }, + { + "last-modified": "Wed, 14 Mar 2012 14:03:29 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 166, + "wire": "88e5dedd588ca47e561cc5802171c03410ff6496d07abe940bca681d8a0801654086e36edc0bea62d1bf6c96df697e9403aa612c6a080112816ae36e5c69b53168dfc4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=11660411" + }, + { + "expires": "Mon, 18 Mar 2013 11:57:19 GMT" + }, + { + "last-modified": "Tue, 07 Feb 2012 14:56:45 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 167, + "wire": "88e8e1e0588ba47e561cc5802cb6e3eebd6496d07abe940bea693f75040089403771b66e09c53168df6c96e4593e94032a6a225410022500cdc0357190a98b46ffc7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1356978" + }, + { + "expires": "Mon, 19 Nov 2012 05:53:26 GMT" + }, + { + "last-modified": "Wed, 03 Oct 2012 03:04:31 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 168, + "wire": "88ebe4e3588ca47e561cc58020101a0b8dff6496c361be940054d03b141002ca816ee09cb8cb2a62d1bf6c96d07abe940894d03b1410022500edc6deb81754c5a37fca", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=10204165" + }, + { + "expires": "Fri, 01 Mar 2013 15:26:33 GMT" + }, + { + "last-modified": "Mon, 12 Mar 2012 07:58:17 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 169, + "wire": "88eee7e6588ba47e561cc581f744e3e0736496dd6d5f4a09a53096350400b2a00571b15c0b4a62d1bf6c96c361be94132a681d8a080112807ee01cb8db6a62d1bfcd", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=9726906" + }, + { + "expires": "Sun, 24 Feb 2013 02:52:14 GMT" + }, + { + "last-modified": "Fri, 23 Mar 2012 09:06:55 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 170, + "wire": "88f1eae9588ba47e561cc5804271b69b176496df3dbf4a09f5349fba820044a05eb816ae34053168df6c96e4593e940894dc5ad4100225002b8215c034a62d1bffd0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=2265452" + }, + { + "expires": "Thu, 29 Nov 2012 18:14:40 GMT" + }, + { + "last-modified": "Wed, 12 Sep 2012 02:22:04 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 171, + "wire": "88f4ec7f3a88cc52d6b4341bb97f0f0d8379d703dcf2588ba47e561cc5804dbe20001ff1768586b19272ffd37b8b84842d695b05443c86aa6fe0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "8761" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + }, + { + "transfer-encoding": "chunked" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 172, + "wire": "88f85f87352398ac4c697ff5d57f0388ea52d6b0e83772ffc0c1f7c2e20f0d836de13df5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "5828" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 173, + "wire": "88fabf0f0d8371a641f6bec1f7c2f5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "6430" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 174, + "wire": "88fabff6d6bec0c1f7c2e20f0d8371e7c5f5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "6892" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 175, + "wire": "88fabf0f0d8369c65f6c96c361be940094d27eea0801128105c082e32da98b46ffbfc2f6f8c3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "4639" + }, + { + "last-modified": "Fri, 02 Nov 2012 10:10:35 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "accept-ranges": "bytes" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + } + ] + }, + { + "seqno": 176, + "wire": "88fbc0f7d7bfc1c2f8c3e30f0d03383533f6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "853" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 177, + "wire": "88fbf4f3588ba47e561cc581c799684f356496d07abe941054ca3a941002ca816ee08371b1298b46ff6c96df697e9413ea681fa5040089403d700edc65f53168dfda", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=6834284" + }, + { + "expires": "Mon, 21 Jan 2013 15:21:52 GMT" + }, + { + "last-modified": "Tue, 29 May 2012 08:07:39 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 178, + "wire": "886196dc34fd280654d27eea0801128115c6ddb80794c5a37ff8f7588ba47e561cc581e085d03adf6496df697e94036a612c6a0801654086e341b8d32a62d1bf6c96dd6d5f4a09f521aec504008940b7704edc6dd53168dfde", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=8117075" + }, + { + "expires": "Tue, 05 Feb 2013 11:41:43 GMT" + }, + { + "last-modified": "Sun, 29 Apr 2012 15:27:57 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 179, + "wire": "88c1fbfa588ba47e561cc581c081e136df6496dd6d5f4a0595328ea50400b2a01bb8d06e09953168df6c96c361be940b6a65b6a50400894033704f5c65e53168dfe1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=6108255" + }, + { + "expires": "Sun, 13 Jan 2013 05:41:23 GMT" + }, + { + "last-modified": "Fri, 15 Jun 2012 03:28:38 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 180, + "wire": "88c4768b1d6324e5502b857138b83f5f88352398ac74acb37f588ba47e561cc580416dd69f736496e4593e9413ca693f75040089408ae05bb82694c5a37f6c96c361be940b4a6e2d6a080112816ae0817196d4c5a37fe6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=2157496" + }, + { + "expires": "Wed, 28 Nov 2012 12:15:24 GMT" + }, + { + "last-modified": "Fri, 14 Sep 2012 14:20:35 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 181, + "wire": "88c9c2c1588ba47e561cc581d680e040ff6496d07abe9413ca651d4a08016540397022b81754c5a37f6c96e4593e940b8a681fa5040089400ae09cb8d3ea62d1bfe9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=7406109" + }, + { + "expires": "Mon, 28 Jan 2013 06:12:17 GMT" + }, + { + "last-modified": "Wed, 16 May 2012 02:26:49 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 182, + "wire": "88ccc5c4588ba47e561cc581b7dd7df6dc6496c361be940854ca3a941002ca817ae019b80694c5a37f6c96d07abe940bca65b6a5040089400ae34ddc0b6a62d1bfec", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=5979956" + }, + { + "expires": "Fri, 11 Jan 2013 18:03:04 GMT" + }, + { + "last-modified": "Mon, 18 Jun 2012 02:45:15 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 183, + "wire": "88cfc8c7588ba47e561cc580227c2c84206496d07abe94005486bb141002ca8266e36ddc65e53168df6c96d07abe9403ea651d4a080112816ee001700ea98b46ffef", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=12913110" + }, + { + "expires": "Mon, 01 Apr 2013 23:55:38 GMT" + }, + { + "last-modified": "Mon, 09 Jan 2012 15:00:07 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 184, + "wire": "88d25f86497ca582211f6c96c361be940094d27eea0801128166e32edc6c4a62d1bff1d9dbdc5a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "text/css" + }, + { + "last-modified": "Fri, 02 Nov 2012 13:37:52 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 185, + "wire": "88d5db0f0d8369c65f6c96df3dbf4a002a693f7504008940b3704ddc0054c5a37fdbde6496d07abe94032a5f2914100225022b8dbb700f298b46ffe052848fd24a8f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "4639" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:08 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 186, + "wire": "886196dc34fd280654d27eea0801128115c6ddb807d4c5a37fdfc1f6dee0e16496d07abe94032a5f2914100225022b8dbb700fa98b46ffe3c30f0d8365c759c0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "3673" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 187, + "wire": "88bfe00f0d03383334c2dfe2bee3c0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "834" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 188, + "wire": "88dac56c96c361be940094d27eea080112807ae321b81694c5a37ff8e0e2e3c4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "text/css" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:31:14 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 189, + "wire": "88c0e1dff8e0e2e3bfe4c40f0d836de13dc1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Fri, 02 Nov 2012 10:10:35 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "5828" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 190, + "wire": "88c0e10f0d03353836c3e0e3c1bfe4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "586" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "accept-ranges": "bytes" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + } + ] + }, + { + "seqno": 191, + "wire": "88c0e1c3f8e0e2e3bfe4c40f0d03353831c1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "581" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 192, + "wire": "88c0d4d3588ba47e561cc581b75a71b7d96496e4593e9403ea651d4a0801654006e059b8d094c5a37f6c96dc34fd282654cb6d4a0801128115c135700ca98b46fffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=5746593" + }, + { + "expires": "Wed, 09 Jan 2013 01:13:42 GMT" + }, + { + "last-modified": "Sat, 23 Jun 2012 12:24:03 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 193, + "wire": "88c3d7d6588ba47e561cc581a6840101ef6496d07abe94134a5f291410022502e5c69db81754c5a37f6c96df697e94134a65b6850400894037702e5c6c4a62d1bf798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4420208" + }, + { + "expires": "Mon, 24 Dec 2012 16:47:17 GMT" + }, + { + "last-modified": "Tue, 24 Jul 2012 05:16:52 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 194, + "wire": "88e25f91497ca589d34d1f6a1271d882a60c57737fed0f0d83136d836c96df697e94640a681d8a080102807ee09bb8d814c5a37f6496d07abe94032a5f2914100225022b8dbb700d298b46ffeecbedc1ec40864d832148790b9365a13aeb4279a6c0d01b0b4fb4d8021032207fcf0f28adf06416290bdcc42c00fb50be6b3585441badabe94032b693f758400b2a04571b76e01d53168dff6a5634cf031f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "text/html; charset=GBK" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "2550" + }, + { + "last-modified": "Tue, 30 Mar 2010 09:25:50 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:04 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + }, + { + "transfer-encoding": "chunked" + }, + { + "vary": "Accept-Encoding" + }, + { + "tracecode": "34277428450405149450110320" + }, + { + "content-encoding": "gzip" + }, + { + "set-cookie": "wise_device=0; expires=Sun, 03-Nov-2013 12:57:07 GMT; path=/" + } + ] + }, + { + "seqno": 195, + "wire": "88cbdfde588ba47e561cc581d6c0fb2cff6496d07abe940894d27eea080112806ee322b8d094c5a37f6c96e4593e940baa6a225410022500cdc69cb801298b46ffc5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=750933" + }, + { + "expires": "Mon, 12 Nov 2012 05:32:42 GMT" + }, + { + "last-modified": "Wed, 17 Oct 2012 03:46:02 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 196, + "wire": "88e9e2e1588ca47e561cc58190ba069f79ff6496df697e94036a693f750400b2a04371b66e32ea98b46f6c96dd6d5f4a321535112a080102816ee01ab807d4c5a37fc8", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=31704989" + }, + { + "expires": "Tue, 05 Nov 2013 11:53:37 GMT" + }, + { + "last-modified": "Sun, 31 Oct 2010 15:04:09 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 197, + "wire": "88d1e5e4588ba47e561cc5804d32e3adbb6496dc34fd2800a97ca4504008940bb71a7ee34e298b46ff6c96dc34fd280794dc5ad410022500cdc086e36d298b46ffcb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=2436757" + }, + { + "expires": "Sat, 01 Dec 2012 17:49:46 GMT" + }, + { + "last-modified": "Sat, 08 Sep 2012 03:11:54 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 198, + "wire": "88d4e8e7588aa47e561cc5802079b6416496dd6d5f4a01a5349fba820044a05fb806ee36fa98b46f6c96df3dbf4a002a693f750400894002e32fdc13ea62d1bfce", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=108530" + }, + { + "expires": "Sun, 04 Nov 2012 19:05:59 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 00:39:29 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 199, + "wire": "88d7ebea588aa47e561cc581b0be21336496c361be9403ea693f7504008940b37020b8d894c5a37f6c96d07abe941094d444a820044a045704fdc69953168dffd1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=519223" + }, + { + "expires": "Fri, 09 Nov 2012 13:10:52 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 12:29:43 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 200, + "wire": "88daeeed588ba47e561cc581979c032f0b6496df697e940bca5f291410022500ddc0b971b0a98b46ff6c96d07abe94038a436cca080112806ae05db8d36a62d1bfd4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3860382" + }, + { + "expires": "Tue, 18 Dec 2012 05:16:51 GMT" + }, + { + "last-modified": "Mon, 06 Aug 2012 04:17:45 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 201, + "wire": "88dd5f911d75d0620d263d4c795ba0fb8d04b0d5a76c96df697e94640a6a225410022500fdc106e36e298b46ffd6408721eaa8a4498f5788ea52d6b0e83772ff7b8b84842d695b05443c86aa6f768586b19272ffe1588ba47e561cc5804dbe20001fe7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Tue, 30 Oct 2012 09:21:56 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 202, + "wire": "88e35f87352398ac4c697f0f0d8313c207e7c2c0e3bfe5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "2820" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 203, + "wire": "88e4bee7dbc2c1c0e3bfe80f0d03363138e5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "618" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 204, + "wire": "88e4bee7dbc2c1c0e3bfe80f0d03353732e5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "572" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 205, + "wire": "88e4f8f7588ba47e561cc581f69f780d8b6496df3dbf4a082a612c6a0801654086e05eb800a98b46ff6c96e4593e9413ca681d8a0801128172e05bb82694c5a37fde", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=9498052" + }, + { + "expires": "Thu, 21 Feb 2013 11:18:01 GMT" + }, + { + "last-modified": "Wed, 28 Mar 2012 16:15:24 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 206, + "wire": "88e7fbfa588ba47e561cc581a6da0bed8b6496e4593e94138a5f2914100225002b8cb9704153168dff6c96dc34fd2820a996da1410022500fdc65eb8d36a62d1bfe1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4541952" + }, + { + "expires": "Wed, 26 Dec 2012 02:36:21 GMT" + }, + { + "last-modified": "Sat, 21 Jul 2012 09:38:45 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 207, + "wire": "88ea768b1d6324e5502b857138b83f5f88352398ac74acb37f588ba47e561cc581a79b69a6db6496dc34fd2827d4be522820044a05db826ae34d298b46ff6c96dc34fd281694cb6d0a080112806ae00371b794c5a37fe6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4854455" + }, + { + "expires": "Sat, 29 Dec 2012 17:24:44 GMT" + }, + { + "last-modified": "Sat, 14 Jul 2012 04:01:58 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 208, + "wire": "88efc2c1588aa47e561cc580417597db6496df697e94038a693f750400894006e081704d298b46ff6c96d07abe9413ea6a2254100225022b8105c65f53168dffe9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=217395" + }, + { + "expires": "Tue, 06 Nov 2012 01:20:24 GMT" + }, + { + "last-modified": "Mon, 29 Oct 2012 12:10:39 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 209, + "wire": "88f2c5c4588aa47e561cc581c03820b56496dc34fd281029a4fdd410022502cdc102e34ca98b46ff6c95dc34fd282029a8895040089408ae041700053168dfec", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=606214" + }, + { + "expires": "Sat, 10 Nov 2012 13:20:43 GMT" + }, + { + "last-modified": "Sat, 20 Oct 2012 12:10:00 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 210, + "wire": "88f5c8c7588ba47e561cc58020009a08816496e4593e9413aa612c6a08016540b3704ddc69f53168df6c96c361be940b8a681d8a080112810dc6dfb8d3ea62d1bfef", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=10024120" + }, + { + "expires": "Wed, 27 Feb 2013 13:25:49 GMT" + }, + { + "last-modified": "Fri, 16 Mar 2012 11:59:49 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 211, + "wire": "88f8cbca588ba47e561cc581a6dd642f836496e4593e94138a5f2914100225021b8172e36fa98b46ff6c96c361be941014cb6d0a0801128172e05db82794c5a37ff2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4573190" + }, + { + "expires": "Wed, 26 Dec 2012 11:16:59 GMT" + }, + { + "last-modified": "Fri, 20 Jul 2012 16:17:28 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 212, + "wire": "88fbcecd588ba47e561cc581e75d65c13d6496e4593e940b2a612c6a080165400ae01ab81754c5a37f6c96dc34fd28169486bb14100225020b8d0ae36253168dfff5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=8773628" + }, + { + "expires": "Wed, 13 Feb 2013 02:04:17 GMT" + }, + { + "last-modified": "Sat, 14 Apr 2012 10:42:52 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 213, + "wire": "886196dc34fd280654d27eea0801128115c6ddb807d4c5a37fd90f0d84081b71cf6c96df3dbf4a002a693f7504008940b3704ddc0054c5a37fdedc6496d07abe94032a5f2914100225022b8dbb700fa98b46ffdc52848fd24a8f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "10566" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 214, + "wire": "88c15f86497ca582211fc1fae1e0df5a839bd9abc1df", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "text/css" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + } + ] + }, + { + "seqno": 215, + "wire": "88c3bf6c96c361be940094d27eea080112807ae321b81694c5a37f798624f6d5d4b27fe4e3e2c0c3e1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "text/css" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:31:14 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + } + ] + }, + { + "seqno": 216, + "wire": "88c5e66c96c361be940094d27eea080112810dc03f702053168dffbfe5e4e3c1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 217, + "wire": "88c6e10f0d03383430c5e5e3c4e2c3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "840" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 218, + "wire": "88c6e10f0d840800107fc5e5e3c4e2c3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "10010" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 219, + "wire": "88c6e10f0d836df13fc5e5e3c3c4e2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "5929" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "accept-ranges": "bytes" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + } + ] + }, + { + "seqno": 220, + "wire": "88c65f87352398ac5754df0f0d830bee83c1e6e4c5e3c4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "1970" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:31:14 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 221, + "wire": "88c7be0f0d8313cebd6c96c361be940094d27eea0801128166e32edc6c4a62d1bfe7e5c6e4c5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2878" + }, + { + "last-modified": "Fri, 02 Nov 2012 13:37:52 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 222, + "wire": "88c8bf0f0d83132e3bbee7e5c6e4c5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2367" + }, + { + "last-modified": "Fri, 02 Nov 2012 13:37:52 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 223, + "wire": "88c8e30f0d033133386c96df697e94134a65b685040089403371b7ee09953168dfe8e6c7e5c6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "138" + }, + { + "last-modified": "Tue, 24 Jul 2012 03:59:23 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 224, + "wire": "88c9c00f0d03323438c1e8e6c7e5c6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "248" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 225, + "wire": "88c9c00f0d03333430c1e8e6c7e5c6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "340" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 226, + "wire": "88c9c00f0d826c42c1e8e6c7e5c6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "522" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 227, + "wire": "88c9e40f0d83089e6bc1e8e6c7e5c6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1284" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 228, + "wire": "88c9e40f0d820b42c1e8e6c7e5c6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "142" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 229, + "wire": "88c9dddc588ba47e561cc5804f38ebcd3f6496df3dbf4a01c52f948a0801128176e32d5c65e53168df6c96e4593e9413ea436cca0801128066e342b810a98b46ffc5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=2867849" + }, + { + "expires": "Thu, 06 Dec 2012 17:34:38 GMT" + }, + { + "last-modified": "Wed, 29 Aug 2012 03:42:11 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 230, + "wire": "88cce0df588aa47e561cc581d684e89a6496d07abe940894d27eea0801128066e05bb8db2a62d1bf6c96e4593e940baa6a225410022500f5c0bf71a0a98b46ffc8", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=742724" + }, + { + "expires": "Mon, 12 Nov 2012 03:15:53 GMT" + }, + { + "last-modified": "Wed, 17 Oct 2012 08:19:41 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 231, + "wire": "88cfe3e2588ba47e561cc5802ebaebef876496dc34fd282694d27eea0801128015c6c1704053168dff6c96dd6d5f4a09953716b5040089403f7020b8d3aa62d1bfcb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1777991" + }, + { + "expires": "Sat, 24 Nov 2012 02:50:20 GMT" + }, + { + "last-modified": "Sun, 23 Sep 2012 09:10:47 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 232, + "wire": "88d2e6e5588ba47e561cc581f704cbef7f6496e4593e940b4a693f7504008940b9702edc03aa62d1bf6c96c361be940894d444a820044a01cb8176e044a62d1bffce", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=962398" + }, + { + "expires": "Wed, 14 Nov 2012 16:17:07 GMT" + }, + { + "last-modified": "Fri, 12 Oct 2012 06:17:12 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 233, + "wire": "88d5e9e8588ca47e561cc5802d044dbafb5f6496df697e940b8a435d8a0801654002e34edc032a62d1bf6c96d07abe940894be522820042a059b8176e080a62d1bffd1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=14125794" + }, + { + "expires": "Tue, 16 Apr 2013 00:47:03 GMT" + }, + { + "last-modified": "Mon, 12 Dec 2011 13:17:20 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 234, + "wire": "88d8f9d2d1f7f6f5d30f0d03393534d6f4d5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:31:14 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "954" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 235, + "wire": "88d8eceb588aa47e561cc581a642d81c6496df3dbf4a01e5349fba820044a04571a7ae36da98b46f6c96e4593e94134a6a225410022502cdc0b3719754c5a37fd4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=431506" + }, + { + "expires": "Thu, 08 Nov 2012 12:48:55 GMT" + }, + { + "last-modified": "Wed, 24 Oct 2012 13:13:37 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 236, + "wire": "88dbefee588ca47e561cc5802169e75e0b9f6496dc34fd281714d03b141002ca8115c002e34da98b46ff6c96dc34fd2810a984b1a820044a05ab8d3f71b754c5a37fd7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=11487816" + }, + { + "expires": "Sat, 16 Mar 2013 12:00:45 GMT" + }, + { + "last-modified": "Sat, 11 Feb 2012 14:49:57 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 237, + "wire": "88def2f1588ba47e561cc5804eb2f3edbd6496e4593e94036a5f291410022500ddc69cb82754c5a37f6c96dc34fd2800a9b8b5a820044a019b817ae32253168dffda", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=2738958" + }, + { + "expires": "Wed, 05 Dec 2012 05:46:27 GMT" + }, + { + "last-modified": "Sat, 01 Sep 2012 03:18:32 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 238, + "wire": "88e1f5f4588ba47e561cc581b6dc71b0376496dd6d5f4a01c5328ea50400b2a099b8115c0b4a62d1bf6c96e4593e9413aa65b6a504008940b9704e5c6df53168dfdd", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=5566505" + }, + { + "expires": "Sun, 06 Jan 2013 23:12:14 GMT" + }, + { + "last-modified": "Wed, 27 Jun 2012 16:26:59 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 239, + "wire": "88e45f87352398ac4c697f0f0d83640e336c96c361be940094d27eea0801128105c082e32e298b46ff408721eaa8a4498f5788ea52d6b0e83772ff768586b19272ffe6588ba47e561cc5804dbe20001fe6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "3063" + }, + { + "last-modified": "Fri, 02 Nov 2012 10:10:36 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 240, + "wire": "88e9e00f0d03353339e1c0bfe7bee6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "539" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 241, + "wire": "88e9c2e8e2c07b8b84842d695b05443c86aa6fc0e8bfe50f0d840ba2139fe7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "17226" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 242, + "wire": "88eac30f0d03383333e9c1c0e8bfe7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "833" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 243, + "wire": "886196dc34fd280654d27eea0801128115c6ddb810298b46ffc40f0d827840eac2c1e86496d07abe94032a5f2914100225022b8dbb702053168dffc1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "820" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "accept-ranges": "bytes" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:10 GMT" + }, + { + "cache-control": "max-age=2592000" + } + ] + }, + { + "seqno": 244, + "wire": "88ece36c96e4593e940b4a6e2d6a08010a8072e04571a714c5a37fe6c4c1c3ebc2e80f0d8369f087ea", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Wed, 14 Sep 2011 06:12:46 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "4911" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 245, + "wire": "88c0e40f0d830b2fb5e5c4c3bfc2ea", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "1394" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:10 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 246, + "wire": "88ede40f0d836dd745e7c4c3ebc2ea", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "5772" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:31:14 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 247, + "wire": "88edc6ece6c4c1c3ebc2e80f0d84081f6dafea", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "10954" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 248, + "wire": "88c0e40f0d03393934e5c4c3bfc2ea", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "994" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:10 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 249, + "wire": "88ed768b1d6324e5502b857138b83f5f88352398ac74acb37f588ca47e561cc581b680d38d01bf6496df697e941094cb6d0a0801694006e360b8cb4a62d1bf6c96d07abe940054cb6d4a08007d4086e041702f298b46ffeb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=54046405" + }, + { + "expires": "Tue, 22 Jul 2014 01:50:34 GMT" + }, + { + "last-modified": "Mon, 01 Jun 2009 11:10:18 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 250, + "wire": "88c5c2c1588aa47e561cc581d03af3a16496dd6d5f4a042a693f7504008940bb7196ee002a62d1bf6c96df3dbf4a05e535112a0801128066e341b82754c5a37fee", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=707871" + }, + { + "expires": "Sun, 11 Nov 2012 17:35:01 GMT" + }, + { + "last-modified": "Thu, 18 Oct 2012 03:41:27 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 251, + "wire": "88c8c5c4588aa47e561cc5819704008b6496e4593e9403aa693f7504008940bb71905c684a62d1bf6c96c361be94138a6a225410022500cdc6c1700e298b46fff1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=362012" + }, + { + "expires": "Wed, 07 Nov 2012 17:30:42 GMT" + }, + { + "last-modified": "Fri, 26 Oct 2012 03:50:06 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 252, + "wire": "88cbc8c7588ca47e561cc5802f3afb2e099f6496dd6d5f4a01f532db528200595001b826ae05953168df6c96c361be94138a436cca08010a8115c033700d298b46fff4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=18793623" + }, + { + "expires": "Sun, 09 Jun 2013 01:24:13 GMT" + }, + { + "last-modified": "Fri, 26 Aug 2011 12:03:04 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 253, + "wire": "88cecbca588aa47e561cc581a640d85c6496df3dbf4a01e5349fba820044a04571915c138a62d1bf6c96e4593e94134a6a225410022502cdc69cb8cbaa62d1bff7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=430516" + }, + { + "expires": "Thu, 08 Nov 2012 12:32:26 GMT" + }, + { + "last-modified": "Wed, 24 Oct 2012 13:46:37 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 254, + "wire": "88d1cecd588ca47e561cc581913cf01c03bf6496df697e940bea693f750400b2a005704edc0baa62d1bf6c96d07abe94034a6a225410020500fdc6dcb8db6a62d1bffa", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=32880607" + }, + { + "expires": "Tue, 19 Nov 2013 02:27:17 GMT" + }, + { + "last-modified": "Mon, 04 Oct 2010 09:56:55 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 255, + "wire": "88d4d1d0588ba47e561cc581965c744e356496e4593e940894be522820044a045702f5c0b4a62d1bff6c96c361be940baa436cca080112816ae05bb801298b46ff798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3367264" + }, + { + "expires": "Wed, 12 Dec 2012 12:18:14 GMT" + }, + { + "last-modified": "Fri, 17 Aug 2012 14:15:02 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 256, + "wire": "88d8d5d4588ba47e561cc58196da65e7dc6496c361be940b4a5f291410022502cdc10ae01c53168dff6c96d07abe940b2a436cca0801128115c03b702f298b46ffc1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3543896" + }, + { + "expires": "Fri, 14 Dec 2012 13:22:06 GMT" + }, + { + "last-modified": "Mon, 13 Aug 2012 12:07:18 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 257, + "wire": "88dbe10f0d8313a26f6c96df3dbf4a002a693f7504008940b3704ddc0054c5a37fe0dfdbde52848fd24a8f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "2725" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:10 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 258, + "wire": "886196dc34fd280654d27eea0801128115c6ddb807d4c5a37f5f911d75d0620d263d4c795ba0fb8d04b0d5a76c96c361be940094d27eea080112810dc6dab82794c5a37fc6e4e1e36496d07abe94032a5f2914100225022b8dbb700fa98b46ffe35a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:54:28 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 259, + "wire": "88c25f87352398ac5754df0f0d8369e133e1e7e6c0e5c4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "4823" + }, + { + "last-modified": "Wed, 14 Sep 2011 06:12:46 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 260, + "wire": "88e3e9c5c9e7e4e6e2e5bf0f0d03353938c4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:10 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "598" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 261, + "wire": "88e3be0f0d8369969a6c96c361be9403ea6e2d6a08010a8076e09fb820298b46ffe8e7e3e6c5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "4344" + }, + { + "last-modified": "Fri, 09 Sep 2011 07:29:20 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:10 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 262, + "wire": "88e4e1e0588aa47e561cc581f101c6436496e4593e940b4a693f75040089403571a0dc0054c5a37f6c96dc34fd281654d444a820044a01bb827ee09d53168dffcd", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=920631" + }, + { + "expires": "Wed, 14 Nov 2012 04:41:01 GMT" + }, + { + "last-modified": "Sat, 13 Oct 2012 05:29:27 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 263, + "wire": "88e7e4e3588ba47e561cc58020780103c06496c361be9403ca681d8a08016540b3702ddc0814c5a37f6c96d07abe9413aa612c6a0801128115c106e040a62d1bffd0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=10801080" + }, + { + "expires": "Fri, 08 Mar 2013 13:15:10 GMT" + }, + { + "last-modified": "Mon, 27 Feb 2012 12:21:10 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 264, + "wire": "88eae7e6588ca47e561cc5802fb2d3edb8f76496dc34fd2816d4cb6d4a0801654086e34fdc6de53168df6c96dc34fd28165486d99410021502ddc086e32d298b46ffd3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=19349568" + }, + { + "expires": "Sat, 15 Jun 2013 11:49:58 GMT" + }, + { + "last-modified": "Sat, 13 Aug 2011 15:11:34 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 265, + "wire": "88edeae9588ba47e561cc5804dbeeb60736496d07abe94032a5f291410022502d5c13d71b714c5a37f6c96df697e94034a6e2d6a080112807ee36cdc65d53168dfd6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=2597506" + }, + { + "expires": "Mon, 03 Dec 2012 14:28:56 GMT" + }, + { + "last-modified": "Tue, 04 Sep 2012 09:53:37 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 266, + "wire": "88f0edec588ca47e561cc5804e882f3ceb3f6496dc34fd281694dc5ad41002ca8166e34ddc032a62d1bf6c96dc34fd28112984b1a820042a0437041b82654c5a37ffd9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=27218873" + }, + { + "expires": "Sat, 14 Sep 2013 13:45:03 GMT" + }, + { + "last-modified": "Sat, 12 Feb 2011 11:21:23 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 267, + "wire": "88f3f0ef588ba47e561cc581965d75d75b6496e4593e940894be522820044a05bb8166e09b53168dff6c96c361be940baa436cca080112807ae09ab8cbea62d1bfdc", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3377775" + }, + { + "expires": "Wed, 12 Dec 2012 15:13:25 GMT" + }, + { + "last-modified": "Fri, 17 Aug 2012 08:24:39 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 268, + "wire": "88f6f3f2588ba47e561cc581a134e800f76496dc34fd2821297ca4504008940b971a05c65e53168dff6c96dc34fd282794cb6d0a080112806ee320b81694c5a37fdf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4247008" + }, + { + "expires": "Sat, 22 Dec 2012 16:40:38 GMT" + }, + { + "last-modified": "Sat, 28 Jul 2012 05:30:14 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 269, + "wire": "88f9f6f5588ba47e561cc5802f05b6da676496dc34fd282694d27eea0801128166e05cb81654c5a37f6c96dc34fd282129b8b5a820044a045702fdc034a62d1bffe2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1815543" + }, + { + "expires": "Sat, 24 Nov 2012 13:16:13 GMT" + }, + { + "last-modified": "Sat, 22 Sep 2012 12:19:04 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 270, + "wire": "886196dc34fd280654d27eea0801128115c6ddb810298b46fffaf9588aa47e561cc5804e81d0836496df697e94038a693f7504008940b9700fdc0014c5a37f6c96dd6d5f4a09e535112a0801128072e32cdc640a62d1bfe6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=270710" + }, + { + "expires": "Tue, 06 Nov 2012 16:09:00 GMT" + }, + { + "last-modified": "Sun, 28 Oct 2012 06:33:30 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 271, + "wire": "88e0df6c96c361be940094d27eea080112807ae321b81694c5a37fe7408721eaa8a4498f5788ea52d6b0e83772ff7b8b84842d695b05443c86aa6f768586b19272ffe0e1588ba47e561cc5804dbe20001f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:31:14 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + } + ] + }, + { + "seqno": 272, + "wire": "88c6768b1d6324e5502b857138b83f5f88352398ac74acb37f588ba47e561cc5802ebe07c2df6496dc34fd282694d27eea0801128072e09bb8d36a62d1bf6c96dd6d5f4a09953716b5040089400ae001700053168dfff0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1790915" + }, + { + "expires": "Sat, 24 Nov 2012 06:25:45 GMT" + }, + { + "last-modified": "Sun, 23 Sep 2012 02:00:00 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 273, + "wire": "88cbc2c1588aa47e561cc5802d082f7f6496dc34fd280654d27eea0801128172e36d5c03ca62d1bf6c96dc34fd280654d27eea080112806ee019b81694c5a37ff3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=14218" + }, + { + "expires": "Sat, 03 Nov 2012 16:54:08 GMT" + }, + { + "last-modified": "Sat, 03 Nov 2012 05:03:14 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 274, + "wire": "88cec5c4588ba47e561cc5819744c842f76496dd6d5f4a05c52f948a080112816ee01fb80794c5a37f6c96df3dbf4a01f521b665040089403d71966e05953168dff6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3723118" + }, + { + "expires": "Sun, 16 Dec 2012 15:09:08 GMT" + }, + { + "last-modified": "Thu, 09 Aug 2012 08:33:13 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 275, + "wire": "88d1c8c7588ba47e561cc581a7196db6596496df3dbf4a09d52f948a080112806ae32e5c032a62d1bf6c96df3dbf4a05f532db42820044a01bb8cbf704d298b46ff9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4635533" + }, + { + "expires": "Thu, 27 Dec 2012 04:36:03 GMT" + }, + { + "last-modified": "Thu, 19 Jul 2012 05:39:24 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 276, + "wire": "88d4cbca588ca47e561cc580220b2f3a107f6496dd6d5f4a09a5340ec50400b2a00171a7ee000a62d1bf6c96c361be9413aa651d4a0801128166e059b8c814c5a37f798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=12138710" + }, + { + "expires": "Sun, 24 Mar 2013 00:49:00 GMT" + }, + { + "last-modified": "Fri, 27 Jan 2012 13:13:30 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 277, + "wire": "88d8cfce588ca47e561cc5802fb2e84427ff6496dc34fd2816d4cb6d4a08016540bb71b05c6df53168df6c96dc34fd28165486d99410021500cdc03f7190a98b46ffc1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=19371229" + }, + { + "expires": "Sat, 15 Jun 2013 17:50:59 GMT" + }, + { + "last-modified": "Sat, 13 Aug 2011 03:09:31 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 278, + "wire": "88dbd2d1588ba47e561cc58191004cb4ef6496d07abe940814be522820044a05ab827ee32ea98b46ff6c96df697e94105486d99410022500fdc6c5702da98b46ffc4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3202347" + }, + { + "expires": "Mon, 10 Dec 2012 14:29:37 GMT" + }, + { + "last-modified": "Tue, 21 Aug 2012 09:52:15 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 279, + "wire": "88ded5d4588ba47e561cc581971d79a65d6496dd6d5f4a05c52f948a0801128015c69ab82754c5a37f6c96c361be94081486d99410022500fdc10ae32e298b46ffc7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3678437" + }, + { + "expires": "Sun, 16 Dec 2012 02:44:27 GMT" + }, + { + "last-modified": "Fri, 10 Aug 2012 09:22:36 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 280, + "wire": "88e1d8d7588ba47e561cc5802079b6c4cf6496c361be940b8a693f75040089400ae09fb81654c5a37f6c96df697e9403ea6a225410022500fdc6d9b80694c5a37fca", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1085523" + }, + { + "expires": "Fri, 16 Nov 2012 02:29:13 GMT" + }, + { + "last-modified": "Tue, 09 Oct 2012 09:53:04 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 281, + "wire": "886196dc34fd280654d27eea0801128115c6ddb807d4c5a37f5f911d75d0620d263d4c795ba0fb8d04b0d5a7e2cce1e0df5a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:31:14 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 282, + "wire": "88e7dedd588ba47e561cc581b0844268406496e4593e940bca65b6a50400b4a01bb8cbb7190298b46f6c96dc34fd28079486d9941000fa8066e32e5c640a62d1bfd0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=51122420" + }, + { + "expires": "Wed, 18 Jun 2014 05:37:30 GMT" + }, + { + "last-modified": "Sat, 08 Aug 2009 03:36:30 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 283, + "wire": "88c3c26c96c361be940094d27eea0801128166e32edc6c4a62d1bfd1e6e5e4c2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Fri, 02 Nov 2012 13:37:52 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 284, + "wire": "88ebe2e1588ba47e561cc581a65e0bef356496d07abe94134a5f291410022500e5c082e05a53168dff6c96e4593e94136a65b685040089400ae321b800a98b46ffd4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4381984" + }, + { + "expires": "Mon, 24 Dec 2012 06:10:14 GMT" + }, + { + "last-modified": "Wed, 25 Jul 2012 02:31:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 285, + "wire": "88eee5e4588ba47e561cc581d640d85b6f6496dd6d5f4a09d5328ea50400b2a005700fdc69b53168df6c96c361be940bca681fa50400894082e322b800298b46ffd7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=7305155" + }, + { + "expires": "Sun, 27 Jan 2013 02:09:45 GMT" + }, + { + "last-modified": "Fri, 18 May 2012 10:32:00 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 286, + "wire": "88f1e8e7588ca47e561cc5802cb2d000277f6496dc34fd28071486bb141002ca8215c64171b754c5a37f6c96c361be94640a5f291410021502edc69fb8cb8a62d1bfda", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=13340027" + }, + { + "expires": "Sat, 06 Apr 2013 22:30:57 GMT" + }, + { + "last-modified": "Fri, 30 Dec 2011 17:49:36 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 287, + "wire": "88f4ebea588ba47e561cc58196c2ebef356496c361be940b4a5f291410022500e5c082e05a53168dff6c96df697e940b4a436cca0801128015c643700253168dffdd", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3517984" + }, + { + "expires": "Fri, 14 Dec 2012 06:10:14 GMT" + }, + { + "last-modified": "Tue, 14 Aug 2012 02:31:02 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 288, + "wire": "88f7eeed588ba47e561cc581a79d136d0b6496dc34fd2827d4be522820044a085704e5c0894c5a37ff6c96c361be940b2a65b68504008940bb71b7ee01b53168dfe0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4872542" + }, + { + "expires": "Sat, 29 Dec 2012 22:26:12 GMT" + }, + { + "last-modified": "Fri, 13 Jul 2012 17:59:05 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 289, + "wire": "88d35f87352398ac5754df0f0d8469e7590f6c96c361be940094d27eea080112810dc03f702053168dfff7f56496d07abe94032a5f2914100225022b8dbb700fa98b46fff552848fd24a8f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "48731" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 290, + "wire": "886196dc34fd280654d27eea0801128115c6ddb810298b46fff6f5588ba47e561cc5802f36dbee3b6496dd6d5f4a09b5349fba820044a001704fdc6dd53168df6c96c361be941054dc5ad410022502cdc6c3719714c5a37fe8", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1855967" + }, + { + "expires": "Sun, 25 Nov 2012 00:29:57 GMT" + }, + { + "last-modified": "Fri, 21 Sep 2012 13:51:36 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 291, + "wire": "88c1f9f8588ba47e561cc5802cb6e89c0f6496d07abe940bea693f75040089403771b7ae042a62d1bf6c96e4593e94032a6a2254100225002b8db7700f298b46ffeb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1357261" + }, + { + "expires": "Mon, 19 Nov 2012 05:58:11 GMT" + }, + { + "last-modified": "Wed, 03 Oct 2012 02:55:08 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 292, + "wire": "88c4768b1d6324e5502b857138b83f5f88352398ac74acb37f588ba47e561cc5802213a100416496d07abe94136a681d8a08016540b37196ae000a62d1bf6c96df697e94134a651d4a080112810dc699b827d4c5a37ff0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=12271010" + }, + { + "expires": "Mon, 25 Mar 2013 13:34:00 GMT" + }, + { + "last-modified": "Tue, 24 Jan 2012 11:43:29 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 293, + "wire": "886196dc34fd280654d27eea0801128115c6ddb810a98b46ffc3c2588ba47e561cc5802071f6db6f6496df3dbf4a05b5349fba820044a085700cdc036a62d1bf6c96df697e9403ea6a225410022502f5c69bb820298b46fff4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1069555" + }, + { + "expires": "Thu, 15 Nov 2012 22:03:05 GMT" + }, + { + "last-modified": "Tue, 09 Oct 2012 18:45:20 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 294, + "wire": "88c1c6c5588ba47e561cc5819085a0b2f76496dd6d5f4a01f52f948a0801128166e36fdc13ea62d1bf6c96df3dbf4a099521b6650400894082e362b8cb6a62d1bff7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3114138" + }, + { + "expires": "Sun, 09 Dec 2012 13:59:29 GMT" + }, + { + "last-modified": "Thu, 23 Aug 2012 10:52:35 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 295, + "wire": "88c4c9c8588ca47e561cc5802d89b68210ff6496d07abe9413ea435d8a080165400ae045704253168dff6c96e4593e940b8a693f750400854082e09cb8d3ca62d1bffa", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=15254111" + }, + { + "expires": "Mon, 29 Apr 2013 02:12:22 GMT" + }, + { + "last-modified": "Wed, 16 Nov 2011 10:26:48 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 296, + "wire": "88d35f87352398ac4c697f0f0d8371e65a6c96df3dbf4a002a693f7504008940b3704ddc0054c5a37f408721eaa8a4498f5788ea52d6b0e83772ff768586b19272ffd86496d07abe94032a5f2914100225022b8dbb702053168dff588ba47e561cc5804dbe20001f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:10 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "6834" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "accept-ranges": "bytes" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:10 GMT" + }, + { + "cache-control": "max-age=2592000" + } + ] + }, + { + "seqno": 297, + "wire": "88cdd2d1588ba47e561cc5802fb6e09b676496d07abe94138a693f7504008940357041b82694c5a37f6c96e4593e940bea6e2d6a0801128072e01eb8d34a62d1bf798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1956253" + }, + { + "expires": "Mon, 26 Nov 2012 04:21:24 GMT" + }, + { + "last-modified": "Wed, 19 Sep 2012 06:08:44 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 298, + "wire": "88d1d6d5588ba47e561cc58190b8d38fb96496d07abe940814be522820044a01ab8015c03aa62d1bff6c96e4593e94109486d99410022500e5c69db817d4c5a37fc1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3164696" + }, + { + "expires": "Mon, 10 Dec 2012 04:02:07 GMT" + }, + { + "last-modified": "Wed, 22 Aug 2012 06:47:19 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 299, + "wire": "88d4d9d8588ca47e561cc5802db4e38f3e2f6496e4593e940054d03f4a08016540b3702f5c69953168df6c96c361be940854d27eea08010a8115c0b5700e298b46ffc4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=15466892" + }, + { + "expires": "Wed, 01 May 2013 13:18:43 GMT" + }, + { + "last-modified": "Fri, 11 Nov 2011 12:14:06 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 300, + "wire": "88d7dcdb588aa47e561cc581b700c8bf6496dd6d5f4a01a5349fba820044a01ab8c86e01953168df6c96c361be940094d27eea080112806ee34fdc138a62d1bfc7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=56032" + }, + { + "expires": "Sun, 04 Nov 2012 04:31:03 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 05:49:26 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 301, + "wire": "88dadfde588ba47e561cc581d704c85c7f6496e4593e94640a651d4a08016540bd71905c0014c5a37f6c96c361be940854d03f4a080112800dc6c37191298b46ffca", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=7623169" + }, + { + "expires": "Wed, 30 Jan 2013 18:30:00 GMT" + }, + { + "last-modified": "Fri, 11 May 2012 01:51:32 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 302, + "wire": "88dde2e1588ca47e561cc580227c4d38e37f6496df697e94009486bb141002ca8066e01eb81714c5a37f6c96d07abe9403ea651d4a080112807ae32ddc0054c5a37fcd", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=12924665" + }, + { + "expires": "Tue, 02 Apr 2013 03:08:16 GMT" + }, + { + "last-modified": "Mon, 09 Jan 2012 08:35:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 303, + "wire": "88e0e5e4588ba47e561cc5804175c0803f6496e4593e9413ca693f7504008940bb704ddc644a62d1bf6c96c361be940b4a6e2d6a080112806ae001704f298b46ffd0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=2176101" + }, + { + "expires": "Wed, 28 Nov 2012 17:25:32 GMT" + }, + { + "last-modified": "Fri, 14 Sep 2012 04:00:28 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 304, + "wire": "88e3e8e7588ca47e561cc5802d000e85d6bf6496dd6d5f4a05a521aec50400b2a05bb8d82e01b53168df6c96df3dbf4a05b52f948a08010a8076e043704ca98b46ffd3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=14007174" + }, + { + "expires": "Sun, 14 Apr 2013 15:50:05 GMT" + }, + { + "last-modified": "Thu, 15 Dec 2011 07:11:23 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 305, + "wire": "88e6ebea588ba47e561cc5802d32f3cdb76496df697e941014d27eea080112806ae32f5c038a62d1bf6c96d07abe940054d444a820044a01bb8cb7704153168dffd6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1438855" + }, + { + "expires": "Tue, 20 Nov 2012 04:38:06 GMT" + }, + { + "last-modified": "Mon, 01 Oct 2012 05:35:21 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 306, + "wire": "88e9eeed588ba47e561cc581c644cb8dbd6496df697e940b6a651d4a08016540bb7190dc13ea62d1bf6c96dd6d5f4a040a65b6a5040089403371a7ae32d298b46fd9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=6323658" + }, + { + "expires": "Tue, 15 Jan 2013 17:31:29 GMT" + }, + { + "last-modified": "Sun, 10 Jun 2012 03:48:34 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 307, + "wire": "88ecf1f0588aa47e561cc581a0b810ff6496dd6d5f4a01a5349fba820044a00171905c684a62d1bf6c96c361be940094d27eea0801128166e360b80794c5a37fdc", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=41611" + }, + { + "expires": "Sun, 04 Nov 2012 00:30:42 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 13:50:08 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 308, + "wire": "88eff4f3588ca47e561cc5804d81d105c6bf6496df697e94101486d9941002ca8176e09cb8cb6a62d1bf6c96dd6d5f4a019521aec5040085403371b7ae09953168dfdf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=25072164" + }, + { + "expires": "Tue, 20 Aug 2013 17:26:35 GMT" + }, + { + "last-modified": "Sun, 03 Apr 2011 03:58:23 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 309, + "wire": "88f2f7f6588ca47e561cc5802c800d32177f6496e4593e94032a435d8a0801654006e05bb8d3ca62d1bf6c96dc34fd280754ca3a94100225022b817ee36ea98b46ffe2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=13004317" + }, + { + "expires": "Wed, 03 Apr 2013 01:15:48 GMT" + }, + { + "last-modified": "Sat, 07 Jan 2012 12:19:57 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 310, + "wire": "88f5faf9588aa47e561cc581d75e005b6496d07abe940894d27eea0801128166e01ab80714c5a37f6c96df697e940b8a6a2254100225022b8d33704153168dffe5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=778015" + }, + { + "expires": "Mon, 12 Nov 2012 13:04:06 GMT" + }, + { + "last-modified": "Tue, 16 Oct 2012 12:43:21 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 311, + "wire": "88f8768b1d6324e5502b857138b83f5f88352398ac74acb37f588ba47e561cc5802ebaf32cb56496dc34fd282694d27eea0801128015c6dcb806d4c5a37f6c96dd6d5f4a09953716b5040089403d71b7ee09953168dfea", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1778334" + }, + { + "expires": "Sat, 24 Nov 2012 02:56:05 GMT" + }, + { + "last-modified": "Sun, 23 Sep 2012 08:59:23 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 312, + "wire": "886196dc34fd280654d27eea0801128115c6ddb810a98b46ffc3c2588ba47e561cc581b6d90b626f6496dd6d5f4a01c5328ea50400b2a059b827ee05c53168df6c96df3dbf4a09e532db52820044a04371b66e000a62d1bfee", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=5531525" + }, + { + "expires": "Sun, 06 Jan 2013 13:29:16 GMT" + }, + { + "last-modified": "Thu, 28 Jun 2012 11:53:00 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 313, + "wire": "88c1c6c5588ba47e561cc5804d38f38eb76496dd6d5f4a004a5f2914100225002b8d06e34e298b46ff6c96c361be9403aa6e2d6a080112807ee09eb800298b46fff1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=2468675" + }, + { + "expires": "Sun, 02 Dec 2012 02:41:46 GMT" + }, + { + "last-modified": "Fri, 07 Sep 2012 09:28:00 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 314, + "wire": "88c4c9c8588ba47e561cc58022132f36cf6496dc34fd281754d27eea0801128172e36d5c69a53168df6c96dc34fd280714d444a820044a01bb8015c034a62d1bfff4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1223853" + }, + { + "expires": "Sat, 17 Nov 2012 16:54:44 GMT" + }, + { + "last-modified": "Sat, 06 Oct 2012 05:02:04 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 315, + "wire": "88c7cccb588ba47e561cc581969f704fb76496c361be940b4a5f2914100225000b807ae34e298b46ff6c96df697e940b4a436cca080112816ae32d5c0054c5a37ff7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3496295" + }, + { + "expires": "Fri, 14 Dec 2012 00:08:46 GMT" + }, + { + "last-modified": "Tue, 14 Aug 2012 14:34:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 316, + "wire": "886196dc34fd280654d27eea0801128115c6ddb807d4c5a37f5f911d75d0620d263d4c795ba0fb8d04b0d5a76c96df3dbf4a002a693f7504008940b3704ddc0054c5a37ffa408721eaa8a4498f5788ea52d6b0e83772ff7b8b84842d695b05443c86aa6f768586b19272ff6496d07abe94032a5f2914100225022b8dbb700fa98b46ff588ba47e561cc5804dbe20001f5a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:09 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:09 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 317, + "wire": "88d3d8d7588ca47e561cc580400b6003ce7f6496d07abe94134a65b6a50400b2a05eb810dc6dd53168df6c96df697e94138a65b685040085400ae09db8cbea62d1bf798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=20150086" + }, + { + "expires": "Mon, 24 Jun 2013 18:11:57 GMT" + }, + { + "last-modified": "Tue, 26 Jul 2011 02:27:39 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 318, + "wire": "88d7dcdb588ba47e561cc581b036ebefbf6496df697e940054ca3a941002ca800dc6ddb810298b46ff6c96d07abe9403ea65b6850400894082e36edc0b2a62d1bfc1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:11 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=5057999" + }, + { + "expires": "Tue, 01 Jan 2013 01:57:10 GMT" + }, + { + "last-modified": "Mon, 09 Jul 2012 10:57:13 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 319, + "wire": "886196dc34fd280654d27eea0801128115c6ddb811298b46ffe0df588ba47e561cc5804e059680df6496d07abe94032a5f291410022502f5c6d9b8dbaa62d1bf6c96df697e94034a6e2d6a080112800dc03371a0a98b46ffc5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=2613405" + }, + { + "expires": "Mon, 03 Dec 2012 18:53:57 GMT" + }, + { + "last-modified": "Tue, 04 Sep 2012 01:03:41 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 320, + "wire": "88c1e3e2588ba47e561cc581a65c6da6596496d07abe94134a5f2914100225001b8cb5704da98b46ff6c96e4593e94136a65b6850400894086e342b8d36a62d1bfc8", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4365433" + }, + { + "expires": "Mon, 24 Dec 2012 01:34:25 GMT" + }, + { + "last-modified": "Wed, 25 Jul 2012 11:42:45 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 321, + "wire": "88c4e6e5588ca47e561cc5802fbccb4d3c0f6496c361be941054cb6d4a080165400ae321b8d894c5a37f6c96df697e94009486d99410021500fdc69db8d894c5a37fcb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=19834480" + }, + { + "expires": "Fri, 21 Jun 2013 02:31:52 GMT" + }, + { + "last-modified": "Tue, 02 Aug 2011 09:47:52 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 322, + "wire": "88c7e9e8588aa47e561cc58021138f7f6496dc34fd280654d27eea0801128172e01bb800298b46ff6c96dc34fd280654d27eea0801128072e341b8cb6a62d1bfce", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=11268" + }, + { + "expires": "Sat, 03 Nov 2012 16:05:00 GMT" + }, + { + "last-modified": "Sat, 03 Nov 2012 06:41:35 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 323, + "wire": "88caeceb588ba47e561cc581b702e09c776496d07abe9403aa651d4a08016540b37001b8cbea62d1bf6c96df697e94138a65b6a5040089408ae34f5c0baa62d1bfd1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=5616267" + }, + { + "expires": "Mon, 07 Jan 2013 13:01:39 GMT" + }, + { + "last-modified": "Tue, 26 Jun 2012 12:48:17 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 324, + "wire": "88cdefee588ba47e561cc581d138cb6c8b6496dc34fd282714ca3a941002ca816ae32e5c034a62d1bf6c96dc34fd2817d4d03f4a080112807ee32fdc13aa62d1bfd4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=7263532" + }, + { + "expires": "Sat, 26 Jan 2013 14:36:04 GMT" + }, + { + "last-modified": "Sat, 19 May 2012 09:39:27 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 325, + "wire": "887688cbbb58980ae05c5f6196dc34fd280654d27eea0801128115c6ddb80794c5a37ff3df0f0d847da6da6b588ca47e561cc58190b6cb80003f0f1390fe420642175a001e7c2d38ebee85efe76496df697e94101486d9941002ca8166e341b817d4c5a37f6c96dd6d5f4a01e532db42820044a05ab8176e01f53168df", + "headers": [ + { + ":status": "200" + }, + { + "server": "JSP2/1.0.2" + }, + { + "date": "Sat, 03 Nov 2012 12:57:08 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "94544" + }, + { + "cache-control": "max-age=31536000" + }, + { + "etag": "\"1031174008914679718\"" + }, + { + "expires": "Tue, 20 Aug 2013 13:41:19 GMT" + }, + { + "last-modified": "Sun, 08 Jul 2012 14:17:09 GMT" + } + ] + }, + { + "seqno": 326, + "wire": "88d55f87352398ac4c697f7f2488cc52d6b4341bb97f0f0d033732336c96df697e94134a65b685040089403371b7ee09953168df6496d07abe94032a5f2914100225022b8dbb702253168dffe252848fd24a8fe5dee640864d832148790b9365a13aeb4279a6c0d01b0b4fb4d8021032207fe30f28adf06416290bdcc42c00fb50be6b3585441badabe94032b693f758400b2a04571b76e01d53168dff6a5634cf031f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/gif" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "723" + }, + { + "last-modified": "Tue, 24 Jul 2012 03:59:23 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + }, + { + "transfer-encoding": "chunked" + }, + { + "vary": "Accept-Encoding" + }, + { + "tracecode": "34277428450405149450110320" + }, + { + "content-encoding": "gzip" + }, + { + "set-cookie": "wise_device=0; expires=Sun, 03-Nov-2013 12:57:07 GMT; path=/" + } + ] + }, + { + "seqno": 327, + "wire": "88dbc30f0d8365d13de9e8e6c0e4bf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "3728" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 328, + "wire": "88dbc30f0d03393837e9e8e6c0e4bf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "987" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 329, + "wire": "88dbc30f0d8364207be9e8e6c0e4bf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "3108" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:25:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 330, + "wire": "88dbea6c96d07abe9413ea6a225410022500fdc03b71a0298b46ffe0e9e8e7c1e5e40f0d03353938c0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Mon, 29 Oct 2012 09:07:40 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "598" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 331, + "wire": "88dceb0f0d033731376c96c361be940094d27eea0801128166e32edc6c4a62d1bfeae8c2e6c1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "717" + }, + { + "last-modified": "Fri, 02 Nov 2012 13:37:52 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 332, + "wire": "88ddec6c96c361be940094d27eea080112810dc03f702053168dffe2ebeae9c3e7e60f0d03393733c2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "973" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 333, + "wire": "88de5f87352398ac5754df0f0d03333836bfeceac4e8c3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "386" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 334, + "wire": "88dfee6c96e4593e94136a65b685040089403b7000b8d34a62d1bfe4edecebe8c5e90f0d03353834c4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Wed, 25 Jul 2012 07:00:44 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-length": "584" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 335, + "wire": "88e05f86497ca582211f6c96e4593e94640a681fa50400894033702d5c65b53168dfe6efeeedc7ebea0f0d03343239c6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "text/css" + }, + { + "last-modified": "Wed, 30 May 2012 03:14:35 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "429" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 336, + "wire": "88e2f10f0d8313a26f6c96c361be940094d27eea080112807ae321b81694c5a37ff0eec8ecc7e7efeb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "2725" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:31:14 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "transfer-encoding": "chunked" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 337, + "wire": "88e3cb0f0d0234336c96df3dbf4a05a535112a0801028105c082e34da98b46fff1efc86496d07abe94032a5f2914100225022b8dbb702053168dffee", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "last-modified": "Thu, 14 Oct 2010 10:10:45 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "accept-ranges": "bytes" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:10 GMT" + }, + { + "cache-control": "max-age=2592000" + } + ] + }, + { + "seqno": 338, + "wire": "88e5c40f0d830b8cb3c5f2f0caeec9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "1633" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 339, + "wire": "88e5cd0f0d03373333c0f2f0caeec9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "733" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:31:14 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 340, + "wire": "88e5c40f0d830b8d3b6c96c361be940094d27eea0801128105c69fb807d4c5a37ff3f1cbefca", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "1647" + }, + { + "last-modified": "Fri, 02 Nov 2012 10:49:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 341, + "wire": "88e6768b1d6324e5502b857138b83f5f88352398ac74acb37f588ba47e561cc580220bae321f6496dc34fd281754d27eea080112816ee043700ca98b46ff6c96dc34fd280714d444a820044a01eb827ee32053168dffef", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1217631" + }, + { + "expires": "Sat, 17 Nov 2012 15:11:03 GMT" + }, + { + "last-modified": "Sat, 06 Oct 2012 08:29:30 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 342, + "wire": "88ebc2c1588ba47e561cc581a7d96d975f6496dd6d5f4a32052f948a080112816ee36cdc642a62d1bf6c96df3dbf4a044a65b685040089403b700d5c65953168dff2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4935379" + }, + { + "expires": "Sun, 30 Dec 2012 15:53:31 GMT" + }, + { + "last-modified": "Thu, 12 Jul 2012 07:04:33 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 343, + "wire": "88eec5c4588aa47e561cc581f680f83f6496dd6d5f4a01a5349fba820044a05bb806ee084a62d1bf6c96df3dbf4a002a693f75040089403d71a05c6c4a62d1bff5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=94090" + }, + { + "expires": "Sun, 04 Nov 2012 15:05:22 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 08:40:52 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 344, + "wire": "88f1c8c7588aa47e561cc5804e3c20376496df697e94038a693f7504008940b7704edc0baa62d1bf6c96dd6d5f4a09e535112a0801128076e36edc0054c5a37ff8", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=268205" + }, + { + "expires": "Tue, 06 Nov 2012 15:27:17 GMT" + }, + { + "last-modified": "Sun, 28 Oct 2012 07:57:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 345, + "wire": "88f4cbca588ba47e561cc58196c0175f076496c361be940b4a5f2914100225001b8d02e084a62d1bff6c96df697e940b4a436cca080112810dc64171b1298b46fffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3501790" + }, + { + "expires": "Fri, 14 Dec 2012 01:40:22 GMT" + }, + { + "last-modified": "Tue, 14 Aug 2012 11:30:52 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 346, + "wire": "88f7cecd588ba47e561cc5802079f65c776496c361be940b8a693f75040089403371966e05f53168df6c96df697e9403ea6a225410022500edc69ab8dbaa62d1bf798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1089367" + }, + { + "expires": "Fri, 16 Nov 2012 03:33:19 GMT" + }, + { + "last-modified": "Tue, 09 Oct 2012 07:44:57 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 347, + "wire": "88fbd2d1588ba47e561cc581b0badbe1776496e4593e940094ca3a941002ca8105c685704fa98b46ff6c96c361be94038a65b68504008940bb704e5c65e53168dfc1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=5175917" + }, + { + "expires": "Wed, 02 Jan 2013 10:42:29 GMT" + }, + { + "last-modified": "Fri, 06 Jul 2012 17:26:38 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 348, + "wire": "886196dc34fd280654d27eea0801128115c6ddb811298b46ffd6d5588ba47e561cc5804f09c7da776496df3dbf4a01c52f948a0801128072e04571b7d4c5a37f6c96df3dbf4a320521b665040089400ae09bb8cbca62d1bfc5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=2826947" + }, + { + "expires": "Thu, 06 Dec 2012 06:12:59 GMT" + }, + { + "last-modified": "Thu, 30 Aug 2012 02:25:38 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 349, + "wire": "88c15f911d75d0620d263d4c795ba0fb8d04b0d5a7e4c67f2b88ea52d6b0e83772ff7b8b84842d695b05443c86aa6f768586b19272ffeb588ba47e561cc5804dbe20001f5a839bd9ab0f0d03353630ec", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Fri, 02 Nov 2012 13:37:52 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "560" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 350, + "wire": "88c7e70f0d8313cd83e3c2c0edbfec", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2850" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:31:14 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 351, + "wire": "88c7e70f0d83085f73e8c2c0edbfec", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-length": "1196" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 352, + "wire": "88c7f00f0d830bc06be8c2c0edbfec", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "1804" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:09:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 353, + "wire": "88c7de0f0d830b22076c96e4593e94640a681fa50400894033702fdc0bca62d1bfc3c1eec0ed", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1320" + }, + { + "last-modified": "Wed, 30 May 2012 03:19:18 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 354, + "wire": "88c8dfbeccc3c2c1eec0bf0f0d830bef83ed", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Wed, 30 May 2012 03:19:18 GMT" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1990" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 355, + "wire": "88c8e0df588ca47e561cc5802d09c640eb9f6496e4593e940baa435d8a08016540b571b6ee01e53168df6c96c361be9403ea5f291410021500fdc006e080a62d1bffcf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=14263076" + }, + { + "expires": "Wed, 17 Apr 2013 14:55:08 GMT" + }, + { + "last-modified": "Fri, 09 Dec 2011 09:01:20 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 356, + "wire": "88cbe3e2588ba47e561cc5804079c65b0f6496df697e9413aa693f7504008940b9704fdc69953168df6c96dd6d5f4a05c53716b5040089403771b15c0814c5a37fd2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=2086351" + }, + { + "expires": "Tue, 27 Nov 2012 16:29:43 GMT" + }, + { + "last-modified": "Sun, 16 Sep 2012 05:52:10 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 357, + "wire": "88cee6e5588ba47e561cc581c65f13c0776496e4593e940b8a651d4a080165408ae34cdc6df53168df6c96c361be9403ca65b6a504008940b3704cdc65e53168dfd5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=6392807" + }, + { + "expires": "Wed, 16 Jan 2013 12:43:59 GMT" + }, + { + "last-modified": "Fri, 08 Jun 2012 13:23:38 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 358, + "wire": "88d1e9e8588ba47e561cc581a65d65b13d6496d07abe94134a5f291410022500cdc69fb820298b46ff6c96e4593e94136a65b685040089403b7022b8db8a62d1bfd8", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4373528" + }, + { + "expires": "Mon, 24 Dec 2012 03:49:20 GMT" + }, + { + "last-modified": "Wed, 25 Jul 2012 07:12:56 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 359, + "wire": "88d4eceb588aa47e561cc581f70006d96496e4593e940b4a693f7504008940b77197ae01b53168df6c96c361be940894d444a820044a01db8cb7704da98b46ffdb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=960053" + }, + { + "expires": "Wed, 14 Nov 2012 15:38:05 GMT" + }, + { + "last-modified": "Fri, 12 Oct 2012 07:35:25 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 360, + "wire": "88d7efee588ba47e561cc5802d34e36db96496df697e941014d27eea0801128072e34e5c13ca62d1bf6c96d07abe940054d444a820044a003702f5c65f53168dffde", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1446556" + }, + { + "expires": "Tue, 20 Nov 2012 06:46:28 GMT" + }, + { + "last-modified": "Mon, 01 Oct 2012 01:18:39 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 361, + "wire": "88daf2f1588ca47e561cc5802cbac800f3ff6496df3dbf4a042a435d8a0801654082e362b800a98b46ff6c96e4593e941054be522820042a05db8076e32ca98b46ffe1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=13730089" + }, + { + "expires": "Thu, 11 Apr 2013 10:52:01 GMT" + }, + { + "last-modified": "Wed, 21 Dec 2011 17:07:33 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 362, + "wire": "88ddf5f4588ba47e561cc581a69a740f3f6496df697e94136a5f2914100225000b816ee082a62d1bff6c96d07abe94132a65b68504008940b57040b8db2a62d1bfe4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4447089" + }, + { + "expires": "Tue, 25 Dec 2012 00:15:21 GMT" + }, + { + "last-modified": "Mon, 23 Jul 2012 14:20:53 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 363, + "wire": "88e0f8f7588ba47e561cc5802069e7d9076496df3dbf4a05b5349fba820044a05cb817ee084a62d1bf6c96e4593e940814d444a820044a01cb8115c6c2a62d1bffe7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1048930" + }, + { + "expires": "Thu, 15 Nov 2012 16:19:22 GMT" + }, + { + "last-modified": "Wed, 10 Oct 2012 06:12:51 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 364, + "wire": "885f8b497ca58e83ee3412c3569f0f138afe5a005970200bef7f3f52848fd24a8f6c96e4593e941014cb6d4a0801128172e32fdc6dd53168dfe0dd0f0d830bc27be67686bbcb73015c1f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/javascript" + }, + { + "etag": "\"4013610198\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Wed, 20 Jun 2012 16:39:57 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1828" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "BWS/1.0" + } + ] + }, + { + "seqno": 365, + "wire": "88e7768b1d6324e5502b857138b83f5f88352398ac74acb37f588ca47e561cc5804cbae36db8f76496d07abe94036a436cca08016540b571905c0014c5a37f6c96df697e94032a681fa5040085403f71b0dc65b53168dff0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=23765568" + }, + { + "expires": "Mon, 05 Aug 2013 14:30:00 GMT" + }, + { + "last-modified": "Tue, 03 May 2011 09:51:35 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 366, + "wire": "88ecc17f2888cc52d6b4341bb97f0f0d837da71c6c96e4593e94136a65b685040089403971b7ae34ea98b46f6496d07abe94032a5f2914100225022b8dbb702253168dffe7c8e8f3e9e6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "9466" + }, + { + "last-modified": "Wed, 25 Jul 2012 06:58:47 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + }, + { + "transfer-encoding": "chunked" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 367, + "wire": "88efc5c4588ba47e561cc5804eb2e09f0f6496e4593e94036a5f291410022500ddc00ae01953168dff6c96dc34fd2800a9b8b5a820044a01ab8d3b7190298b46fff6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=2736291" + }, + { + "expires": "Wed, 05 Dec 2012 05:02:03 GMT" + }, + { + "last-modified": "Sat, 01 Sep 2012 04:47:30 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 368, + "wire": "88f2c8c7588ba47e561cc581d1004d85df6496c361be94136a651d4a0801654106e32fdc03ea62d1bf6c96dd6d5f4a080a681fa504008940bf71966e05e53168dff9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=7202517" + }, + { + "expires": "Fri, 25 Jan 2013 21:39:09 GMT" + }, + { + "last-modified": "Sun, 20 May 2012 19:33:18 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 369, + "wire": "886196dc34fd280654d27eea0801128115c6ddb81654c5a37fcccb588aa47e561cc581f704f05c6496e4593e940b4a693f7504008940b9704d5c03ea62d1bf6c96c361be940894d444a820044a01cb8066e082a62d1bff798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=962816" + }, + { + "expires": "Wed, 14 Nov 2012 16:24:09 GMT" + }, + { + "last-modified": "Fri, 12 Oct 2012 06:03:21 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 370, + "wire": "88fad0cf588ca47e561cc5804f3cfbccb61f6496c361be94034a6a22541002ca8005c0b9704d298b46ff6c96df697e94034a651d4a08010a816ae05eb8d814c5a37fc1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=28898351" + }, + { + "expires": "Fri, 04 Oct 2013 00:16:24 GMT" + }, + { + "last-modified": "Tue, 04 Jan 2011 14:18:50 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 371, + "wire": "88c5d3d2588ca47e561cc5804e3e100997ff6496e4593e940854dc5ad41002ca8005c006e044a62d1bff6c96dc34fd2817d4c258d410021502d5c69fb81694c5a37fc4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=26910239" + }, + { + "expires": "Wed, 11 Sep 2013 00:01:12 GMT" + }, + { + "last-modified": "Sat, 19 Feb 2011 14:49:14 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 372, + "wire": "88c8d6d5588ba47e561cc58190b4d34fff6496e4593e9403aa693f750400894035702f5c0094c5a37f6c96dc34fd282754d444a820044a01cb816ee32d298b46ffc7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=314449" + }, + { + "expires": "Wed, 07 Nov 2012 04:18:02 GMT" + }, + { + "last-modified": "Sat, 27 Oct 2012 06:15:34 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 373, + "wire": "88cbd9d8588ca47e561cc5802eb2065c781f6496e4593e941094d03f4a08016540bf7190dc6d953168df6c96df3dbf4a09f53716b5040085413371a76e36ca98b46fca", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=17303680" + }, + { + "expires": "Wed, 22 May 2013 19:31:53 GMT" + }, + { + "last-modified": "Thu, 29 Sep 2011 23:47:53 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 374, + "wire": "88cedcdb588ba47e561cc581e75a1321736496df697e940894c258d41002ca8176e085704fa98b46ff6c96dd6d5f4a05b521aec50400894035700e5c682a62d1bfcd", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=8742316" + }, + { + "expires": "Tue, 12 Feb 2013 17:22:29 GMT" + }, + { + "last-modified": "Sun, 15 Apr 2012 04:06:41 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 375, + "wire": "88d1dfde588aa47e561cc581f6c206836496e4593e940b4a693f7504008940b3700edc6da53168df6c96c361be940894d444a820044a0457196ee36053168dffd0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=951041" + }, + { + "expires": "Wed, 14 Nov 2012 13:07:54 GMT" + }, + { + "last-modified": "Fri, 12 Oct 2012 12:35:50 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 376, + "wire": "88d4e2e1588ca47e561cc5802265b75f7dbf6496df697e94138a681d8a08016540b371a66e34f298b46f6c96dd6d5f4a084a651d4a080112810dc135700253168dffd3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=12357995" + }, + { + "expires": "Tue, 26 Mar 2013 13:43:48 GMT" + }, + { + "last-modified": "Sun, 22 Jan 2012 11:24:02 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 377, + "wire": "886196dc34fd280654d27eea0801128115c6ddb811298b46ff5f87352398ac5754dfe20f0d84640279ff6c96df697e94640a6a225410022500f5c0b57190298b46ffe1588ba47e561cc5804dbe20001fec768586b19272ffd87b8b84842d695b05443c86aa6f40864d832148790b9365a13aeb4279a6c0d01b0b4fb4d8021032207f5a839bd9ab0f28adf06416290bdcc42c00fb50be6b3585441badabe94032b693f758400b2a04571b76e01d53168dff6a5634cf031f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/png" + }, + { + "connection": "Keep-Alive" + }, + { + "content-length": "30289" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:14:30 GMT" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Apache" + }, + { + "transfer-encoding": "chunked" + }, + { + "vary": "Accept-Encoding" + }, + { + "tracecode": "34277428450405149450110320" + }, + { + "content-encoding": "gzip" + }, + { + "set-cookie": "wise_device=0; expires=Sun, 03-Nov-2013 12:57:07 GMT; path=/" + } + ] + }, + { + "seqno": 378, + "wire": "88dfedec588ba47e561cc581c7c4d32db36496df697e941094ca3a941002ca8172e099b80714c5a37f6c96dd6d5f4a09d5340fd2820044a01cb806ee09d53168dfde", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=6924353" + }, + { + "expires": "Tue, 22 Jan 2013 16:23:06 GMT" + }, + { + "last-modified": "Sun, 27 May 2012 06:05:27 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 379, + "wire": "88e2f0ef588ca47e561cc58196df7de65a7b6496e4593e94136a5f29141002ca806ae09fb8d054c5a37f6c96dc34fd282694cb6d0a080102806ee362b81714c5a37fe1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=35998348" + }, + { + "expires": "Wed, 25 Dec 2013 04:29:41 GMT" + }, + { + "last-modified": "Sat, 24 Jul 2010 05:52:16 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 380, + "wire": "88e5f3f2588aa47e561cc581a03217036497df3dbf4a01e5349fba820044a01ab8db9719694c5a37ff6c96df3dbf4a09b535112a080112806ae36f5c640a62d1bfe4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=403161" + }, + { + "expires": "Thu, 08 Nov 2012 04:56:34 GMT" + }, + { + "last-modified": "Thu, 25 Oct 2012 04:58:30 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 381, + "wire": "88e8f6f5588ca47e561cc5802175f6df79df6496e4593e941014d03b141002ca800dc65db800298b46ff6c96dc34fd280694c258d4100225021b8cbb7197d4c5a37fe7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=11795987" + }, + { + "expires": "Wed, 20 Mar 2013 01:37:00 GMT" + }, + { + "last-modified": "Sat, 04 Feb 2012 11:37:39 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 382, + "wire": "88ebf9f8588ba47e561cc581a69c71c75e6496df697e94136a5f291410022500ddc68371b0a98b46ff6c96d07abe94132a65b6850400894033704edc6dd53168dfea", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4466678" + }, + { + "expires": "Tue, 25 Dec 2012 05:41:51 GMT" + }, + { + "last-modified": "Mon, 23 Jul 2012 03:27:57 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 383, + "wire": "88ee768b1d6324e5502b857138b83f5f88352398ac74acb37f588ba47e561cc581a65e03c26b6496d07abe94134a5f291410022500ddc6c171b754c5a37f6c96e4593e94136a65b6850400894033700fdc69b53168dfef", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4380824" + }, + { + "expires": "Mon, 24 Dec 2012 05:50:57 GMT" + }, + { + "last-modified": "Wed, 25 Jul 2012 03:09:45 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 384, + "wire": "88f3c2c1588ba47e561cc581a65d0002f76496d07abe94134a5f2914100225002b8d82e36153168dff6c96e4593e94136a65b685040089403f700fdc6dd53168dff2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4370018" + }, + { + "expires": "Mon, 24 Dec 2012 02:50:51 GMT" + }, + { + "last-modified": "Wed, 25 Jul 2012 09:09:57 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 385, + "wire": "88f6c5c4588ca47e561cc58042034e38fb7f6496df697e940b8a65b6850400b2a05db8015c03ca62d1bf6c96dd6d5f4a044a65b6a5040085403571a76e084a62d1bff5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=22046695" + }, + { + "expires": "Tue, 16 Jul 2013 17:02:08 GMT" + }, + { + "last-modified": "Sun, 12 Jun 2011 04:47:22 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 386, + "wire": "885f8b497ca58e83ee3412c3569f0f138afe42ebedbecb6cbad7f352848fd24a8f6c96df3dbf4a080a6e2d6a0801128072e320b8c854c5a37fdddb0f0d033439306196dc34fd280654d27eea0801128115c6ddb81654c5a37f76841d6324e5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/javascript" + }, + { + "etag": "\"1795935374\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Thu, 20 Sep 2012 06:30:31 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "490" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache" + } + ] + }, + { + "seqno": 387, + "wire": "88bfcdcc588ba47e561cc5802f059680ff6496dc34fd282694d27eea0801128115c68171a1298b46ff6c96dc34fd282129b8b5a820044a059b8c82e05a53168dff798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1813409" + }, + { + "expires": "Sat, 24 Nov 2012 12:40:42 GMT" + }, + { + "last-modified": "Sat, 22 Sep 2012 13:30:14 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 388, + "wire": "88c3d1d0588ba47e561cc5804cbc10b60f6496dc34fd2800a97ca45040089400ae099b80654c5a37ff6c96dd6d5f4a01f53716b50400894082e01bb8cb2a62d1bfc1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=2381150" + }, + { + "expires": "Sat, 01 Dec 2012 02:23:03 GMT" + }, + { + "last-modified": "Sun, 09 Sep 2012 10:05:33 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 389, + "wire": "88c6d4d3588ca47e561cc5802203cdb407bf6496dc34fd282654d03b141002ca8105c002e34153168dff6c96dc34fd282794ca3a9410022502f5c6c1702ea98b46ffc4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=12085408" + }, + { + "expires": "Sat, 23 Mar 2013 10:00:41 GMT" + }, + { + "last-modified": "Sat, 28 Jan 2012 18:50:17 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 390, + "wire": "88c9d7d6588ba47e561cc581d71c03226f6496df3dbf4a3215328ea50400b2a01ab8d3f702f298b46f6c96df3dbf4a040a681fa50400894037702cdc0094c5a37fc7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=7660325" + }, + { + "expires": "Thu, 31 Jan 2013 04:49:18 GMT" + }, + { + "last-modified": "Thu, 10 May 2012 05:13:02 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 391, + "wire": "88ccdad9588ca47e561cc580407990be067f6496df697e940094cb6d0a08016540b77196ee32e298b46f6c96dd6d5f4a040a65b685040085403b71a05c138a62d1bfca", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=20831903" + }, + { + "expires": "Tue, 02 Jul 2013 15:35:36 GMT" + }, + { + "last-modified": "Sun, 10 Jul 2011 07:40:26 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 392, + "wire": "88f4dc0f0d8468427d9f6c96e4593e94640a681fa50400894033702fdc0bca62d1bf408721eaa8a4498f5788ea52d6b0e83772fff26496d07abe94032a5f2914100225022b8dbb702253168dfff4d4cdf2f0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:12 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "42293" + }, + { + "last-modified": "Wed, 30 May 2012 03:19:18 GMT" + }, + { + "connection": "keep-alive" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:57:12 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "accept-ranges": "bytes" + }, + { + "transfer-encoding": "chunked" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 393, + "wire": "88d2e0df588ca47e561cc5802c884eb2d37f6496c361be94036a435d8a08016540b77022b8dbca62d1bf6c96d07abe940094ca3a9410022500f5c13771a654c5a37fd0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=13227345" + }, + { + "expires": "Fri, 05 Apr 2013 15:12:58 GMT" + }, + { + "last-modified": "Mon, 02 Jan 2012 08:25:43 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 394, + "wire": "88d5e3e2588ba47e561cc581b71b79b0376496df697e9403ca651d4a0801654002e34ddc65e53168df6c96d07abe94136a65b6a504008940b37040b82654c5a37fd3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=5658505" + }, + { + "expires": "Tue, 08 Jan 2013 00:45:38 GMT" + }, + { + "last-modified": "Mon, 25 Jun 2012 13:20:23 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 395, + "wire": "88d8e6e5588ba47e561cc5802cbeeb4ebb6496d07abe940bea693f7504008940bb700f5c640a62d1bf6c96df697e940094d444a820044a01ab8cb5719794c5a37fd6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1397477" + }, + { + "expires": "Mon, 19 Nov 2012 17:08:30 GMT" + }, + { + "last-modified": "Tue, 02 Oct 2012 04:34:38 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 396, + "wire": "88dbe9e8588ba47e561cc5819081e1041f6496dd6d5f4a01f52f948a0801128115c102e34ca98b46ff6c96df3dbf4a099521b66504008940b57020b81654c5a37fd9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3108210" + }, + { + "expires": "Sun, 09 Dec 2012 12:20:43 GMT" + }, + { + "last-modified": "Thu, 23 Aug 2012 14:10:13 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 397, + "wire": "88deeceb588ba47e561cc58020780f09af6496c361be940b8a693f750400894006e04171b754c5a37f6c96df697e9403ea6a2254100225022b827ee34da98b46ffdc", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1080824" + }, + { + "expires": "Fri, 16 Nov 2012 01:10:57 GMT" + }, + { + "last-modified": "Tue, 09 Oct 2012 12:29:45 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 398, + "wire": "88e1efee588ca47e561cc580206da69e703f6496df697e94036a681d8a08016540b5700d5c6da53168df6c96dd6d5f4a01a5340ec50400894082e341b8d814c5a37fdf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=10544861" + }, + { + "expires": "Tue, 05 Mar 2013 14:04:54 GMT" + }, + { + "last-modified": "Sun, 04 Mar 2012 10:41:50 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 399, + "wire": "88e4f2f1588ba47e561cc581f659702ebb6496df697e940bea612c6a08016540b57040b810298b46ff6c96dd6d5f4a002a435d8a0801128105c086e05e53168dffe2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=9336177" + }, + { + "expires": "Tue, 19 Feb 2013 14:20:10 GMT" + }, + { + "last-modified": "Sun, 01 Apr 2012 10:11:18 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 400, + "wire": "886196dc34fd280654d27eea0801128115c6ddb81694c5a37ff6f5588ba47e561cc581f69d038fff6496e4593e940b4a693f75040089408ae00371a654c5a37f6c96c361be940894d444a820044a05ab8d3d702da98b46ffe6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=947069" + }, + { + "expires": "Wed, 14 Nov 2012 12:01:43 GMT" + }, + { + "last-modified": "Fri, 12 Oct 2012 14:48:15 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 401, + "wire": "88c1f9f8588ba47e561cc5802d3acbc0676496df697e941014d27eea080112816ae081719754c5a37f6c96dd6d5f4a32053716b50400894082e041704f298b46ffe9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1473803" + }, + { + "expires": "Tue, 20 Nov 2012 14:20:37 GMT" + }, + { + "last-modified": "Sun, 30 Sep 2012 10:10:28 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 402, + "wire": "88c4768b1d6324e5502b857138b83f5f88352398ac74acb37f588ba47e561cc5802cbedb6e0b6496d07abe940bea693f7504008940b971972e32e298b46f6c96df697e940094d444a820044a01bb8cbd704fa98b46ffee", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1395562" + }, + { + "expires": "Mon, 19 Nov 2012 16:36:36 GMT" + }, + { + "last-modified": "Tue, 02 Oct 2012 05:38:29 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 403, + "wire": "88f3c2c1588ba47e561cc581a03c20b4ff6496df3dbf4a080a5f291410022502f5c6d9b801298b46ff6c96e4593e94005486d994100225001b806ee32da98b46fff1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4082149" + }, + { + "expires": "Thu, 20 Dec 2012 18:53:02 GMT" + }, + { + "last-modified": "Wed, 01 Aug 2012 01:05:35 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 404, + "wire": "88ccc5c4588ba47e561cc581a0080113ff6496df3dbf4a080a5f2914100225001b8cbf704ca98b46ff6c96df3dbf4a004a436cca080112810dc64571b6d4c5a37ff4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4020129" + }, + { + "expires": "Thu, 20 Dec 2012 01:39:23 GMT" + }, + { + "last-modified": "Thu, 02 Aug 2012 11:32:55 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 405, + "wire": "88cfc8c7588ca47e561cc5819036cb22107f6496df697e941094d444a8200595042b826ae05b53168dff6c96dc34fd282754d27eea080102817ae019b811298b46fff7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=30533221" + }, + { + "expires": "Tue, 22 Oct 2013 22:24:15 GMT" + }, + { + "last-modified": "Sat, 27 Nov 2010 18:03:12 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 406, + "wire": "88d2cbca588ba47e561cc581f136079e736496d07abe940bca612c6a08016540b57197ae34053168df6c96df697e94032a435d8a080112807ee32d5c1054c5a37ffa", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=9250886" + }, + { + "expires": "Mon, 18 Feb 2013 14:38:40 GMT" + }, + { + "last-modified": "Tue, 03 Apr 2012 09:34:21 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 407, + "wire": "88d5cecd588ba47e561cc581d75f702db36496c361be940054c258d41002ca817ae32cdc03aa62d1bf6c96d07abe9403aa681fa50400894006e34ddc13ca62d1bf798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=7796153" + }, + { + "expires": "Fri, 01 Feb 2013 18:33:07 GMT" + }, + { + "last-modified": "Mon, 07 May 2012 01:45:28 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 408, + "wire": "88d9d2d1588ca47e561cc58022680cb4f3bf6496e4593e9413aa681d8a080165400ae085700153168dff6c96dc34fd2820a994752820044a041700edc680a62d1bffc1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=12403487" + }, + { + "expires": "Wed, 27 Mar 2013 02:22:01 GMT" + }, + { + "last-modified": "Sat, 21 Jan 2012 10:07:40 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 409, + "wire": "88dcd5d4588aa47e561cc5819684d05c6496e4593e9403aa693f75040089408ae01ab810298b46ff6c96c361be94138a6a225410022502d5c699b821298b46ffc4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=342416" + }, + { + "expires": "Wed, 07 Nov 2012 12:04:10 GMT" + }, + { + "last-modified": "Fri, 26 Oct 2012 14:43:22 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 410, + "wire": "88dfd8d7588ca47e561cc5802c81a69f703f6496e4593e94032a435d8a080165408ae32cdc0b6a62d1bf6c96c361be94038a651d4a0801128166e34ddc0894c5a37fc7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=13044961" + }, + { + "expires": "Wed, 03 Apr 2013 12:33:15 GMT" + }, + { + "last-modified": "Fri, 06 Jan 2012 13:45:12 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 411, + "wire": "88e2dbda588aa47e561cc581f036f3ff6496dd6d5f4a01a5349fba820044a05ab8076e01953168df6c96df3dbf4a002a693f750400894082e32edc65b53168dfca", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=90589" + }, + { + "expires": "Sun, 04 Nov 2012 14:07:03 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 10:37:35 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 412, + "wire": "88e5dedd588ca47e561cc5802c844f36cb9f6496df3dbf4a01a521aec50400b2a04371a72e040a62d1bf6c96e4593e94034a651d4a080112816ee05fb821298b46ffcd", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=13128536" + }, + { + "expires": "Thu, 04 Apr 2013 11:46:10 GMT" + }, + { + "last-modified": "Wed, 04 Jan 2012 15:19:22 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 413, + "wire": "88e8e1e0588ba47e561cc58196db744cbd6496c361be940b4a5f291410022502edc0357191298b46ff6c96d07abe940b2a436cca080112806ae342b8cbaa62d1bfd0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3557238" + }, + { + "expires": "Fri, 14 Dec 2012 17:04:32 GMT" + }, + { + "last-modified": "Mon, 13 Aug 2012 04:42:37 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 414, + "wire": "88ebe4e3588ba47e561cc5804cb4f32cbf6496c361be94640a693f7504008940bb702e5c0b2a62d1bf6c96d07abe940814dc5ad410022500d5c0bf702da98b46ffd3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=2348339" + }, + { + "expires": "Fri, 30 Nov 2012 17:16:13 GMT" + }, + { + "last-modified": "Mon, 10 Sep 2012 04:19:15 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 415, + "wire": "88eee7e6588aa47e561cc5819032071b6496e4593e9403aa693f750400894006e01eb817d4c5a37f6c96dc34fd282754d444a820044a0457196ee01a53168dffd6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=303065" + }, + { + "expires": "Wed, 07 Nov 2012 01:08:19 GMT" + }, + { + "last-modified": "Sat, 27 Oct 2012 12:35:04 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 416, + "wire": "88f1eae9588ba47e561cc5819742c85c7f6496dd6d5f4a05c52f948a0801128115c133704ca98b46ff6c96df3dbf4a01f521b66504008940b5700d5c6db53168dfd9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3713169" + }, + { + "expires": "Sun, 16 Dec 2012 12:23:23 GMT" + }, + { + "last-modified": "Thu, 09 Aug 2012 14:04:55 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 417, + "wire": "88f4edec588ba47e561cc58196db71c6456496c361be940b4a5f291410022502e5c6dab82714c5a37f6c96d07abe940b2a436cca080112806ee00571b0298b46ffdc", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3556632" + }, + { + "expires": "Fri, 14 Dec 2012 16:54:26 GMT" + }, + { + "last-modified": "Mon, 13 Aug 2012 05:02:50 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 418, + "wire": "88f7f0ef588ba47e561cc581a75f64400f6496dc34fd2827d4be522820044a001704cdc6db53168dff6c96dd6d5f4a05b532db42820044a05ab8066e36253168dfdf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4793201" + }, + { + "expires": "Sat, 29 Dec 2012 00:23:55 GMT" + }, + { + "last-modified": "Sun, 15 Jul 2012 14:03:52 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 419, + "wire": "88faf3f2588ba47e561cc581a79e03afff6496c361be9403ea693f7504008940357190dc6d953168df6c96df697e94132a6a225410022500ddc69db8db6a62d1bfe2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=488079" + }, + { + "expires": "Fri, 09 Nov 2012 04:31:53 GMT" + }, + { + "last-modified": "Tue, 23 Oct 2012 05:47:55 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 420, + "wire": "886196dc34fd280654d27eea0801128115c6ddb81694c5a37ff7f6588ca47e561cc580227df784f35f6496df697e94009486bb141002ca8266e32ddc0bca62d1bf6c96dc34fd280754ca3a9410022502ddc683700da98b46ffe6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=12998284" + }, + { + "expires": "Tue, 02 Apr 2013 23:35:18 GMT" + }, + { + "last-modified": "Sat, 07 Jan 2012 15:41:05 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 421, + "wire": "88c1faf9588ca47e561cc5819782fb4279af6496dd6d5f4a05f5328ea50400b4a05ab827ae32f298b46f6c96df3dbf4a019532db52820040a01fb8db5704e298b46fe9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=38194284" + }, + { + "expires": "Sun, 19 Jan 2014 14:28:38 GMT" + }, + { + "last-modified": "Thu, 03 Jun 2010 09:54:26 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 422, + "wire": "886196dc34fd280654d27eea0801128115c6ddb81654c5a37f5f911d75d0620d263d4c795ba0fb8d04b0d5a70f0d840badbad76c96c361be94138a6a225410022500cdc002e000a62d1bff408721eaa8a4498f5788ea52d6b0e83772ff5a839bd9ab768586b19272ff6496dc34fd280654d27eea0801128166e36edc0b2a62d1bf5889a47e561cc58197000f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:13 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "17574" + }, + { + "last-modified": "Fri, 26 Oct 2012 03:00:00 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 03 Nov 2012 13:57:13 GMT" + }, + { + "cache-control": "max-age=3600" + } + ] + }, + { + "seqno": 423, + "wire": "88cc768b1d6324e5502b857138b83f5f88352398ac74acb37f588ba47e561cc58020081a69ef6496df3dbf4a05b5349fba820044a01bb8cbd700253168df6c96df3dbf4a042a6a225410022500cdc65bb8cbaa62d1bff6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1010448" + }, + { + "expires": "Thu, 15 Nov 2012 05:38:02 GMT" + }, + { + "last-modified": "Thu, 11 Oct 2012 03:35:37 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 424, + "wire": "88d1c2c1588ba47e561cc581b75971f07f6496dc34fd281029a4fdd410022500d5c0bd71a694c5a37f6c96dd6d5f4a082a6a225410022500e5c0b5702ca98b46fff9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=573690" + }, + { + "expires": "Sat, 10 Nov 2012 04:18:44 GMT" + }, + { + "last-modified": "Sun, 21 Oct 2012 06:14:13 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 425, + "wire": "88d4c5c4588ba47e561cc581f71f036db96496dc34fd282654c258d41002ca8172e34e5c640a62d1bf6c96dc34fd282694d03b1410022500ddc0bd71a0a98b46ff798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=9690556" + }, + { + "expires": "Sat, 23 Feb 2013 16:46:30 GMT" + }, + { + "last-modified": "Sat, 24 Mar 2012 05:18:41 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 426, + "wire": "88d8c9c8588ca47e561cc58040744e81b73f6496d07abe940054cb6d0a0801654082e09eb810298b46ff6c96df697e940894cb6d0a08010a8176e36ddc1094c5a37fc1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=20727056" + }, + { + "expires": "Mon, 01 Jul 2013 10:28:10 GMT" + }, + { + "last-modified": "Tue, 12 Jul 2011 17:55:22 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 427, + "wire": "88dbcccb588ba47e561cc581b79c740eb96496df3dbf4a040a651d4a0801654082e341b8d814c5a37f6c96e4593e941014cb6d4a0801128176e09eb800a98b46ffc4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=5867076" + }, + { + "expires": "Thu, 10 Jan 2013 10:41:50 GMT" + }, + { + "last-modified": "Wed, 20 Jun 2012 17:28:01 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 428, + "wire": "88decfce588ba47e561cc5802fb2f3c16f6496dd6d5f4a09b5349fba820044a099b8c82e34fa98b46f6c96e4593e940bea6e2d6a080112816ee360b80694c5a37fc7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1938815" + }, + { + "expires": "Sun, 25 Nov 2012 23:30:49 GMT" + }, + { + "last-modified": "Wed, 19 Sep 2012 15:50:04 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 429, + "wire": "88e1d2d1588ca47e561cc5804db41740cbff6496dc34fd28269486d9941002ca8176e05ab8cb2a62d1bf6c96dc34fd282714d03b1410021500d5c10ae32da98b46ffca", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:14 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=25417039" + }, + { + "expires": "Sat, 24 Aug 2013 17:14:33 GMT" + }, + { + "last-modified": "Sat, 26 Mar 2011 04:22:35 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 430, + "wire": "886196dc34fd280654d27eea0801128115c6ddb816d4c5a37fd6d5588ba47e561cc580226de101af6496dd6d5f4a05e5349fba820044a005704edc0bea62d1bf6c96c361be94036a6a225410022500fdc6ddb80714c5a37fce", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1258204" + }, + { + "expires": "Sun, 18 Nov 2012 02:27:19 GMT" + }, + { + "last-modified": "Fri, 05 Oct 2012 09:57:06 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 431, + "wire": "88c1d9d8588ba47e561cc5802f36db8f356496dd6d5f4a09b5349fba820044a001704ddc0bea62d1bf6c96c361be941054dc5ad410022502d5c006e01c53168dffd1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1855684" + }, + { + "expires": "Sun, 25 Nov 2012 00:25:19 GMT" + }, + { + "last-modified": "Fri, 21 Sep 2012 14:01:06 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 432, + "wire": "88c4dcdb588ba47e561cc581c69e7dc6df6496df3dbf4a05d5328ea50400b2a05bb8cbd702d298b46f6c96e4593e94038a65b6a5040089403b7196ee05d53168dfd4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=6489659" + }, + { + "expires": "Thu, 17 Jan 2013 15:38:14 GMT" + }, + { + "last-modified": "Wed, 06 Jun 2012 07:35:17 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 433, + "wire": "88c7dfde588ba47e561cc581a7dd13a06f6496d07abe94642a5f2914100225002b816ee34053168dff6c96e4593e940854cb6d0a0801128105c102e09b53168dffd7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4972705" + }, + { + "expires": "Mon, 31 Dec 2012 02:15:40 GMT" + }, + { + "last-modified": "Wed, 11 Jul 2012 10:20:25 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 434, + "wire": "88cae2e1588ba47e561cc581a03adbeebf6496df3dbf4a080a5f291410022502edc082e05a53168dff6c96e4593e94005486d99410022500d5c643702e298b46ffda", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4075979" + }, + { + "expires": "Thu, 20 Dec 2012 17:10:14 GMT" + }, + { + "last-modified": "Wed, 01 Aug 2012 04:31:16 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 435, + "wire": "88cd7686bbcb73015c1f0f0d0231365f90497ca589d34d1f649c7620a98268faff5885aec3771a4b0f28bbbb7f6dee3876ffef6ec8f0614ead7b9c4fff20a63f3572087a3f7e5a677715bfbb5ea7bf6e3f6a5634cf031f6a487a466aa0b2b5e319a4b5721e9f7f2e88cc52d6b4341bb97f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "BWS/1.0" + }, + { + "content-length": "16" + }, + { + "content-type": "text/html;charset=gbk" + }, + { + "cache-control": "private" + }, + { + "set-cookie": "BDRCVFR[RQbEFtOPS6t]=mbxnW11j9Dfmh7GuZR8mvqV; path=/; domain=rp.baidu.com" + }, + { + "connection": "Keep-Alive" + } + ] + }, + { + "seqno": 436, + "wire": "88d1c10f0d023136c0bf0f28babb7f6dee3876ffedd6a784c01a1fd44ffe414c7e6ae410f47efcb4ceee2b7f76bd4f7edc7ed4ac699e063ed490f48cd541656bc633496ae43d3fbe", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "BWS/1.0" + }, + { + "content-length": "16" + }, + { + "content-type": "text/html;charset=gbk" + }, + { + "cache-control": "private" + }, + { + "set-cookie": "BDRCVFR[74hAi0as9Oc]=mbxnW11j9Dfmh7GuZR8mvqV; path=/; domain=rp.baidu.com" + }, + { + "connection": "Keep-Alive" + } + ] + }, + { + "seqno": 437, + "wire": "88d1c10f0d023136c0bf0f28bbbb7f6dee3876ffef269a3b457a5676dea7ff90531f9ab9043d1fbf2d33bb8adfddaf53dfb71fb52b1a67818fb5243d233550595af18cd25ab90f4fbe", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "BWS/1.0" + }, + { + "content-length": "16" + }, + { + "content-type": "text/html;charset=gbk" + }, + { + "cache-control": "private" + }, + { + "set-cookie": "BDRCVFR[INlq_Cf3RCm]=mbxnW11j9Dfmh7GuZR8mvqV; path=/; domain=rp.baidu.com" + }, + { + "connection": "Keep-Alive" + } + ] + }, + { + "seqno": 438, + "wire": "88d1c10f0d023136c0bf0f28bbbb7f6dee3876ffefc76fcc9a1b6e747ae7ffc8298fcd5c821e8fdf9699ddc56feed7a9efdb8fda958d33c0c7da921e919aa82cad78c6692d5c87a7be", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "BWS/1.0" + }, + { + "content-length": "16" + }, + { + "content-type": "text/html;charset=gbk" + }, + { + "cache-control": "private" + }, + { + "set-cookie": "BDRCVFR[wqXIM55hsyY]=mbxnW11j9Dfmh7GuZR8mvqV; path=/; domain=rp.baidu.com" + }, + { + "connection": "Keep-Alive" + } + ] + }, + { + "seqno": 439, + "wire": "88d15f87497ca589d34d1f0f0d840badbad76c92dc34a9a4fdd45195040b8dbb702da820045ff0efee6496d07abe94138a65b68502fbeea806ee001700053168df5892ace84ac49ca4eb003e94aec2ac49ca4eb003e24085aec1cd48ff86a8eb10649cbf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "content-type": "text/html" + }, + { + "content-length": "17574" + }, + { + "last-modified": "Sat Nov 3 20:57:15 2012" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 26 Jul 1997 05:00:00 GMT" + }, + { + "cache-control": "post-check=0, pre-check=0" + }, + { + "transfer-encoding": "chunked" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 440, + "wire": "88d6eeed588aa47e561cc581a6590b826496df3dbf4a01e5349fba820044a059b8172e32ea98b46f6c96e4593e94134a6a2254100225022b817ae32053168dffe6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=433162" + }, + { + "expires": "Thu, 08 Nov 2012 13:16:37 GMT" + }, + { + "last-modified": "Wed, 24 Oct 2012 12:18:30 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 441, + "wire": "88d9f1f0588aa47e561cc581d005a7836496dd6d5f4a042a693f7504008940b771a7ae32e298b46f6c96df3dbf4a05e535112a0801128076e05ab8cb2a62d1bfe9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=701481" + }, + { + "expires": "Sun, 11 Nov 2012 15:48:36 GMT" + }, + { + "last-modified": "Thu, 18 Oct 2012 07:14:33 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 442, + "wire": "88dcf4f3588ba47e561cc581a75965c73f6496c361be9403ea693f750400894002e09cb8d054c5a37f6c96df697e94132a6a225410022502cdc6deb82654c5a37fec", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=473366" + }, + { + "expires": "Fri, 09 Nov 2012 00:26:41 GMT" + }, + { + "last-modified": "Tue, 23 Oct 2012 13:58:23 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 443, + "wire": "88dff7f6588ba47e561cc581b6c4c81d7b6496dd6d5f4a01c5328ea50400b2a043700f5c65953168df6c97df3dbf4a09e532db52820044a05cb8cb57197d4c5a37ffef", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=5523078" + }, + { + "expires": "Sun, 06 Jan 2013 11:08:33 GMT" + }, + { + "last-modified": "Thu, 28 Jun 2012 16:34:39 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 444, + "wire": "88e2faf9588ca47e561cc5802e3adbe213ff6496df3dbf4a05c5340fd28200595022b8176e34d298b46f6c96e4593e940894d444a820042a05ab8172e05d53168dfff2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=16759229" + }, + { + "expires": "Thu, 16 May 2013 12:17:44 GMT" + }, + { + "last-modified": "Wed, 12 Oct 2011 14:16:17 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 445, + "wire": "88e5768b1d6324e5502b857138b83f5f88352398ac74acb37f588ba47e561cc581a0bcfb4fbf6496dc34fd2821297ca450400894002e342b81694c5a37ff6c96dd6d5f4a09f532db42820044a059b8276e05d53168dff7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=4189499" + }, + { + "expires": "Sat, 22 Dec 2012 00:42:14 GMT" + }, + { + "last-modified": "Sun, 29 Jul 2012 13:27:17 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 446, + "wire": "887688cbbb58980ae05c5feb5f8b497ca58e83ee3412c3569ff97f1a842507417f0f138afe44e01d65d0b4ebbfcf6c96df697e940bca6e2d6a080112806ee34fdc0baa62d1bf6496dd6d5f4a05d5340ec50400b2a01cb8272e05c53168df588ca47e561cc5802db6d880007f7b8b84842d695b05443c86aa6f5a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "server": "JSP2/1.0.2" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "content-type": "text/javascript" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "close" + }, + { + "etag": "\"2607371477\"" + }, + { + "last-modified": "Tue, 18 Sep 2012 05:49:17 GMT" + }, + { + "expires": "Sun, 17 Mar 2013 06:26:16 GMT" + }, + { + "cache-control": "max-age=15552000" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 447, + "wire": "88c5f2c4798624f6d5d4b27fc40f138afe440719109c109dfe7f6c96c361be940094d27eea0801128105c65fb82714c5a37f6496dc34fd280654d27eea0801128166e01fb8cbca62d1bf5889a47e561cc5802f001fc3c2", + "headers": [ + { + ":status": "200" + }, + { + "server": "JSP2/1.0.2" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "content-type": "text/javascript" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "close" + }, + { + "etag": "\"2063226227\"" + }, + { + "last-modified": "Fri, 02 Nov 2012 10:39:26 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 13:09:38 GMT" + }, + { + "cache-control": "max-age=1800" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 448, + "wire": "88f6cecd588ba47e561cc5802cb4dbcf076496d07abe940bea693f75040089400ae34f5c65c53168df6c96e4593e94032a6a225410022500fdc0b5719654c5a37fc4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=1345881" + }, + { + "expires": "Mon, 19 Nov 2012 02:48:36 GMT" + }, + { + "last-modified": "Wed, 03 Oct 2012 09:14:33 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 449, + "wire": "88f9d1d0588aa47e561cc581f700e33f6496dd6d5f4a01a5349fba820044a05bb8cbd702f298b46f6c96df3dbf4a002a693f75040089403b7196ee01e53168dfc7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=96063" + }, + { + "expires": "Sun, 04 Nov 2012 15:38:18 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 07:35:08 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 450, + "wire": "88fcd4d3588ba47e561cc5819702ebae396496dc34fd2816d4be522820044a01fb8db3704153168dff6c96dc34fd2810a90db32820044a05fb806ee01953168dffca", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=3617766" + }, + { + "expires": "Sat, 15 Dec 2012 09:53:21 GMT" + }, + { + "last-modified": "Sat, 11 Aug 2012 19:05:03 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 451, + "wire": "886196dc34fd280654d27eea0801128115c6ddb816d4c5a37fd8d7588ca47e561cc5802169f71b781f6496dc34fd281714d03b141002ca816ae09cb8db6a62d1bf6c96dc34fd2810a984b1a820044a01fb8dbb71b6d4c5a37fce", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=11496580" + }, + { + "expires": "Sat, 16 Mar 2013 14:26:55 GMT" + }, + { + "last-modified": "Sat, 11 Feb 2012 09:57:55 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 452, + "wire": "88c1dbda588aa47e561cc581903a17db6496e4593e9403aa693f75040089400ae05db810298b46ff6c96dc34fd282754d444a820044a041702edc134a62d1bffd1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "server": "apache 1.1.26.0" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=307195" + }, + { + "expires": "Wed, 07 Nov 2012 02:17:10 GMT" + }, + { + "last-modified": "Sat, 27 Oct 2012 10:17:24 GMT" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 453, + "wire": "88d9c45f86497ca582211fd2d80f1389fe5c0bc17c4f32f7f36c96df3dbf4a002a693f75040089408ae34fdc6d953168dfd5d4", + "headers": [ + { + ":status": "200" + }, + { + "server": "JSP2/1.0.2" + }, + { + "date": "Sat, 03 Nov 2012 12:57:15 GMT" + }, + { + "content-type": "text/css" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "close" + }, + { + "etag": "\"618192838\"" + }, + { + "last-modified": "Thu, 01 Nov 2012 12:49:53 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 454, + "wire": "88db6196dc34fd280654d27eea0801128115c6ddb81714c5a37f5f87352398ac5754dfdb0f0d837dd7df0f138afe44ebacb2e85e033fcf52848fd24a8f6c96df697e94640a6a225410022500cdc6deb811298b46ff6496c361be9403ea693f75040089403f71b6ae36e298b46f588aa47e561cc581c034f001", + "headers": [ + { + ":status": "200" + }, + { + "server": "JSP2/1.0.2" + }, + { + "date": "Sat, 03 Nov 2012 12:57:16 GMT" + }, + { + "content-type": "image/png" + }, + { + "connection": "close" + }, + { + "content-length": "9799" + }, + { + "etag": "\"2773371803\"" + }, + { + "accept-ranges": "bytes" + }, + { + "last-modified": "Tue, 30 Oct 2012 03:58:12 GMT" + }, + { + "expires": "Fri, 09 Nov 2012 09:54:56 GMT" + }, + { + "cache-control": "max-age=604800" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_23.json b/http/http-client/src/test/resources/hpack-test-case/story_23.json new file mode 100644 index 000000000..9b6987353 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_23.json @@ -0,0 +1,14132 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "488264016196dc34fd280654d27eea0801128166e32edc0814c5a37f769086b19272b025c4b884a7f5c2a379feff0f28ff3dbb76f2dc325f81b6dd034fc6d842d15c04ae34f8d96df036d05975b1899744eb5215b2040204a1085e13829196d92b426a56dc6c1a0fecd45035452b6a88a05440544f68190d524e89d56635440c9524b42a2068191510356e5440fc54400815115e5598d5102ceeab230b8a88a0544faa2062293a9d514a2004000802a88184d61653f9545285c544507da85f359ac2a20dd6d5f4a0195b49fbac16540b371976e040a62d1bfed4ac699e063ed490f48cd540bc7191721d7b7afdff0f1f909d29aee30c78f1e178e322e43af6f5630f0d82109f408721eaa8a4498f57842507417f5f95497ca589d34d1f6a1271d882a60320eb3cf36fac1f", + "headers": [ + { + ":status": "301" + }, + { + "date": "Sat, 03 Nov 2012 13:37:10 GMT" + }, + { + "server": "Apache/2.2.22 (Unix)" + }, + { + "set-cookie": "BBC-UID=557049b5114e60f649a3590541375a237274de5c1020f1118262d353e424f5650Mozilla%2f5%2e0%20%28Macintosh%3b%20Intel%20Mac%20OS%20X%2010%2e8%3b%20rv%3a16%2e0%29%20Gecko%2f20100101%20Firefox%2f16%2e0; expires=Sun, 03-Nov-13 13:37:10 GMT; path=/; domain=.bbc.co.uk;" + }, + { + "location": "http://www.bbc.co.uk/" + }, + { + "content-length": "229" + }, + { + "connection": "close" + }, + { + "content-type": "text/html; charset=iso-8859-1" + } + ] + }, + { + "seqno": 1, + "wire": "88768586b19272ff588eaec3771a4bf4a523f2b0e62c0e035f87497ca589d34d1f5a839bd9ab6496dc34fd280654d27eea0801128166e32edc65e53168df0f13b1fe64948e3f201970430b617996d98e49247a51824211d24ab34f92594aecaf05d246eb6d48c89a0bafb4400d92bef87f9f4088f2b563a169ce84ff93ac7401a757278f099578e322e43af6f5b8f03f0f0d84136179cf6196dc34fd280654d27eea0801128166e32edc0854c5a37f7f0788ea52d6b0e83772ff408af2b10649cab0c8931eaf90d70eedca7f551ea588324e51c7417fbf4088f2b10649cab0e62f0233337b05582d43444e", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "cache-control": "private, max-age=60" + }, + { + "content-type": "text/html" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Sat, 03 Nov 2012 13:37:38 GMT" + }, + { + "etag": "\"dfc69d0362a1518353bddd8fa0dcc7cf-49cffe7f817cb754d3241794c0a3e991\"" + }, + { + "x-pal-host": "pal047.cwwtf.bbc.co.uk:80" + }, + { + "content-length": "25186" + }, + { + "date": "Sat, 03 Nov 2012 13:37:11 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-action": "PASS (non-cacheable)" + }, + { + "x-cache-age": "33" + }, + { + "vary": "X-CDN" + } + ] + }, + { + "seqno": 2, + "wire": "886196dc34fd280654d27eea0801128166e32edc0894c5a37fc96c96d07abe940b6a6a2254100225020b8d0ae36ea98b46ff52848fd24a8f5888a47e561cc581f0036496dc34fd280654d27eea0801128166e362b810a98b46ff7b8b84842d695b05443c86aa6fcb0f0d8313416b5501304088ea52d6b0e83772ff8d49a929ed4c0d7d2948fcc0175b7f0a88cc52d6b4341bb97f5f911d75d0620d263d4c795ba0fb8d04b0d5a7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:37:12 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Mon, 15 Oct 2012 10:42:57 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=900" + }, + { + "expires": "Sat, 03 Nov 2012 13:52:11 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "2414" + }, + { + "age": "0" + }, + { + "keep-alive": "timeout=4, max=175" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "application/x-javascript" + } + ] + }, + { + "seqno": 3, + "wire": "88d2588aa47e561cc581e71a003f5f89352398ac7958c43d5fd16496dd6d5f4a01a5349fba820044a059b8cbb702253168df0f13b1fe64948e3f201970430b617996d98e49247a51824211d24ab34f92594aecaf05d246eb6d48c89a0bafb4400d92bef87f9fd00f0d03393538cac2cdcccb6c96e4593e9413ca65b6a5040038a05ab8c8ae34ca98b46fc9c57f058d49a929ed4c0d7d2948fcc017c1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "cache-control": "max-age=86400" + }, + { + "content-type": "image/x-icon" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Sun, 04 Nov 2012 13:37:12 GMT" + }, + { + "etag": "\"dfc69d0362a1518353bddd8fa0dcc7cf-49cffe7f817cb754d3241794c0a3e991\"" + }, + { + "x-pal-host": "pal047.cwwtf.bbc.co.uk:80" + }, + { + "content-length": "958" + }, + { + "date": "Sat, 03 Nov 2012 13:37:12 GMT" + }, + { + "connection": "Keep-Alive" + }, + { + "x-cache-action": "PASS (non-cacheable)" + }, + { + "x-cache-age": "33" + }, + { + "vary": "X-CDN" + }, + { + "last-modified": "Wed, 28 Jun 2006 14:32:43 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "0" + }, + { + "keep-alive": "timeout=4, max=190" + } + ] + }, + { + "seqno": 4, + "wire": "88d7c76c96df3dbf4a01b5340fd2820042a01fb816ee36fa98b46fcbd50f0d8369d79cc4588ca47e561cc581903cd36fba0f6496dc34fd282714d444a820059502cdc6dcb8d094c5a37fcfd3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Thu, 05 May 2011 09:15:59 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "4786" + }, + { + "content-type": "application/x-javascript" + }, + { + "cache-control": "max-age=30845970" + }, + { + "expires": "Sat, 26 Oct 2013 13:56:42 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:12 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 5, + "wire": "88769086b19272b025c4b85f53fae151bcff7f0f138ffe6408d66872ba58da92479e781fcf6496df697e940b2a693f750400894086e362b806d4c5a37f588ba47e561cc5804dbe20001fcd6c96d07abe940baa6a225410021502cdc65db821298b46ffd1db0f0d830884ef5f901d75d0620d263d4c741f71a0961ab4ff6196dc34fd280654d27eea0801128166e32edc0b2a62d1bfd9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "etag": "\"d1a-4af7eb4dd8880\"" + }, + { + "expires": "Tue, 13 Nov 2012 11:52:05 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Mon, 17 Oct 2011 13:37:22 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1227" + }, + { + "content-type": "application/javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:37:13 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 6, + "wire": "88c30f138ffe5c75d59a1cae9636a3940d001fcf6496df697e940b2a693f750400894086e34fdc6c2a62d1bfc2d16c96d07abe940baa6a225410021502cdc65db820298b46ffd5df0f0d033634345f86497ca582211fc1dc", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "etag": "\"677-4af7eb4bf0400\"" + }, + { + "expires": "Tue, 13 Nov 2012 11:49:51 GMT" + }, + { + "cache-control": "max-age=2592000" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Mon, 17 Oct 2011 13:37:20 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "644" + }, + { + "content-type": "text/css" + }, + { + "date": "Sat, 03 Nov 2012 13:37:13 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 7, + "wire": "88c66c96d07abe9413ea6a225410022502cdc64371b6d4c5a37f0f138ffe5923eb34491914617191bc407f3fd7588ca47e561cc58190b6cb80003f6496df3dbf4a321535112a080165403d71a76e36253168dfd6e30f0d03343138c5c4df", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Mon, 29 Oct 2012 13:31:55 GMT" + }, + { + "etag": "\"3c9-4cd32b163a8c0\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Thu, 31 Oct 2013 08:47:52 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "418" + }, + { + "content-type": "application/javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:37:13 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 8, + "wire": "88c9c00f138ffe44175668923228c2e3237880fe7fd9bf6496df3dbf4a321535112a080165403d71a7ae01953168dfd7e40f0d821043c6c5e0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Mon, 29 Oct 2012 13:31:55 GMT" + }, + { + "etag": "\"217-4cd32b163a8c0\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Thu, 31 Oct 2013 08:48:03 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "211" + }, + { + "content-type": "application/javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:37:13 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 9, + "wire": "88ca6c96df3dbf4a042a6a2254100225020b807ae09e53168dff0f138ffe5d642e2cd123236400dc925003f9dbc16496df3dbf4a09a535112a080165403d7040b8d894c5a37fd9e60f0d836de6dbc4c7e2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Thu, 11 Oct 2012 10:08:28 GMT" + }, + { + "etag": "\"7316-4cbc5c0a6df00\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Thu, 24 Oct 2013 08:20:52 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "5855" + }, + { + "content-type": "text/css" + }, + { + "date": "Sat, 03 Nov 2012 13:37:13 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 10, + "wire": "88cc6c96d07abe9413ea6a225410022502cdc659b80654c5a37f0f138ffe5d0b4459a248c8a36dd0b41203f9ddc36496df3dbf4a321535112a080165403d71a7ae01d53168dfdbe80f0d836de7c3c6c9e4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Mon, 29 Oct 2012 13:33:03 GMT" + }, + { + "etag": "\"714c-4cd32b57141c0\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Thu, 31 Oct 2013 08:48:07 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "5891" + }, + { + "content-type": "text/css" + }, + { + "date": "Sat, 03 Nov 2012 13:37:13 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 11, + "wire": "88ce6c96e4593e940b8a693f7504008540b771a15c0b8a62d1bf0f138ffe5b2904b3518648e5111e138007f3dfc56496df697e940baa6e2d6a0801654086e34ddc65b53168dfddea0f0d83759799cccbe6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Wed, 16 Nov 2011 15:42:16 GMT" + }, + { + "etag": "\"5ec2-4b1dbf2c82600\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Tue, 17 Sep 2013 11:45:35 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "7383" + }, + { + "content-type": "application/javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:37:13 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 12, + "wire": "88d06c96df3dbf4a042a6a2254100225020b807ae09c53168dff0f1390fe5a0c8facd123236403cf363781fcffe1c76496df3dbf4a09a535112a080165403d7041b810a98b46ffdfeccecd0f0d836dd133e8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Thu, 11 Oct 2012 10:08:26 GMT" + }, + { + "etag": "\"41d9-4cbc5c0885a80\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Thu, 24 Oct 2013 08:21:11 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "application/javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:37:13 GMT" + }, + { + "content-length": "5723" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 13, + "wire": "88d26c96d07abe9413ea6a225410022502cdc645700153168dff0f1390fe5f009f59a248c8a30c72b2e340fe7fe3c9c8e0ed0f0d84085f71dfcfcee9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Mon, 29 Oct 2012 13:32:01 GMT" + }, + { + "etag": "\"9029-4cd32b1bf3640\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Thu, 31 Oct 2013 08:47:52 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "11967" + }, + { + "content-type": "application/javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:37:13 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 14, + "wire": "88f0e06c96d07abe94138a435d8a080102816ae08571b7d4c5a37fe4ee0f0d03393136dd588ca47e561cc5804eb4179f0bbf6496d07abe940b8a6e2d6a0801654106e36fdc0814c5a37fd1ec", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Mon, 26 Apr 2010 14:22:59 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "916" + }, + { + "content-type": "application/x-javascript" + }, + { + "cache-control": "max-age=27418917" + }, + { + "expires": "Mon, 16 Sep 2013 21:59:10 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:13 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 15, + "wire": "88d66c96e4593e94038a65b68504008540bb702d5c036a62d1bf0f1390fe4401808b34375c7e31b32ca1681fcfe7cd6496df697e940baa6e2d6a0801654086e360b80654c5a37fe5f20f0d8465d79f17d46196dc34fd280654d27eea0801128166e32edc0b4a62d1bfef", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Wed, 06 Jul 2011 17:14:05 GMT" + }, + { + "etag": "\"20a0c-4a769ba3ff140\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Tue, 17 Sep 2013 11:50:03 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "37892" + }, + { + "content-type": "application/javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:37:14 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 16, + "wire": "88f6e66c96d07abe94138a435d8a080102816ae05eb8cb6a62d1bfeaf40f0d84642c81efe3588ca47e561cc5804eb4179e7d9f6496d07abe940b8a6e2d6a0801654106e36f5c69d53168dfc1f2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Mon, 26 Apr 2010 14:18:35 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "31308" + }, + { + "content-type": "application/x-javascript" + }, + { + "cache-control": "max-age=27418893" + }, + { + "expires": "Mon, 16 Sep 2013 21:58:47 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:14 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 17, + "wire": "884003703370d6acf4189eac2cb07f33a535dc618f1e3c2f31cf35051c882d9dcc42a1721e962b1cc51c8c56cd6bf9a68fe7e94bdae0fe74eac8a5fc1c54d7ba1535eebea64e30a9938df5356fd6a6ae1b54d5bf6a9934df5356fbdfcf5f96497ca58e83ee3412c3569fb50938ec4153070df8567b0f138f0b80642179965f65d6db6df081a7ff6196dc34fd280654d27eea0801128115c69bb8db4a62d1bf6496dc34fd280654d27eea0801128166e34ddc6da53168df4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb59871a52324f496a4f5a839bd9ab768320e52f0f0d840842d37f408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f5583640f35588faed8e8313e94a47e561cc58197000f", + "headers": [ + { + ":status": "200" + }, + { + "p3p": "policyref=\"http://www.googleadservices.com/pagead/p3p.xml\", CP=\"NOI DEV PSA PSD IVA IVD OTP OUR OTR IND OTC\"" + }, + { + "content-type": "text/javascript; charset=UTF-8" + }, + { + "etag": "16031183393755591049" + }, + { + "date": "Sat, 03 Nov 2012 12:45:54 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 13:45:54 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-disposition": "attachment" + }, + { + "content-encoding": "gzip" + }, + { + "server": "cafe" + }, + { + "content-length": "11145" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "3084" + }, + { + "cache-control": "public, max-age=3600" + } + ] + }, + { + "seqno": 18, + "wire": "88e76c96d07abe9413ea6a225410022502cdc64371b714c5a37f0f138ffe5d1086b344919146174458c00fe7f8de6496df3dbf4a321535112a080165403d71a7ae01c53168dff6c40f0d836de0b3e56196dc34fd280654d27eea0801128166e32edc0bca62d1bf7f3588ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Mon, 29 Oct 2012 13:31:56 GMT" + }, + { + "etag": "\"722a-4cd32b172eb00\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Thu, 31 Oct 2013 08:48:06 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "5813" + }, + { + "content-type": "application/javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:37:18 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 19, + "wire": "887689bf7b3e65a193777b3ff50f0d826402c7c0", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "302" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:18 GMT" + } + ] + }, + { + "seqno": 20, + "wire": "88bef50f0d826402c76196dc34fd280654d27eea0801128166e32edc0bea62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "302" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:19 GMT" + } + ] + }, + { + "seqno": 21, + "wire": "885f87352398ac4c697f6c96df697e9403ca681fa50400894102e01cb81794c5a37f6196c361be940094d27eea080112816ae320b810298b46ff6496dc34fd280654d27eea080112816ae320b810298b46ffce768344b2970f0d023433cb5584799109ff5890aed8e8313e94a47e561cc581e71a003f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Tue, 08 May 2012 20:06:18 GMT" + }, + { + "date": "Fri, 02 Nov 2012 14:30:10 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 14:30:10 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "43" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "83229" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 22, + "wire": "88768c86b19272ad78fe8e92b015c36c96dc34fd280654d27eea0801128166e32edc0bea62d1bf5890a47e561cc581e71a003e94aec3771a4b6496dd6d5f4a01a5349fba820044a059b8cbb702fa98b46f7f1ae9acf4189eac2cb07f33a535dc618e885ec2f7410cbd454b1e19231620d5b35afe69a3f9fa52f6b83f9d3ab4a9af742a6bdd7d4c9c6153271bea6adfad4dd0e853269bea70d3914d7c36a97b568534c3c54c9a77a97f06852f69dea6edf0a9af6e05356fbca63c10ff3f4088f2b5761c8b48348f89ae46568e61a002581f5f9e1d75d0620d263d4c741f71a0961ab4fd9271d882a60c9bb52cf3cdbeb07f798624f6d5d4b27fd77b8b84842d695b05443c86aa6fd1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Sat, 03 Nov 2012 13:37:19 GMT" + }, + { + "cache-control": "max-age=86400, private" + }, + { + "expires": "Sun, 04 Nov 2012 13:37:19 GMT" + }, + { + "p3p": "policyref=\"http://js.revsci.net/w3c/rsip3p.xml\", CP=\"NON PSA PSD IVA IVD OTP SAM IND UNI PUR COM NAV INT DEM CNT STA PRE OTC HEA\"" + }, + { + "x-proc-data": "pd3-bgas02-0" + }, + { + "content-type": "application/javascript;charset=ISO-8859-1" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "date": "Sat, 03 Nov 2012 13:37:18 GMT" + } + ] + }, + { + "seqno": 23, + "wire": "88769086b19272b025c4b85f53fae151bcff7f6c96e4593e94032a6a225410022502d5c102e042a62d1bff0f138ffe432c71acd12313cdb820b64203f952848fd24a8f588ba47e561cc5804dbe20001f6496c361be94640a693f7504008940b771a15c69b53168dfc3dd0f0d83644db55f901d75d0620d263d4c741f71a0961ab4ffd7d6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Wed, 03 Oct 2012 14:20:11 GMT" + }, + { + "etag": "\"1fbb-4cb2856215cc0\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=2592000" + }, + { + "expires": "Fri, 30 Nov 2012 15:42:45 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "3254" + }, + { + "content-type": "application/javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:37:18 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 24, + "wire": "88c36c96df3dbf4a042a6a2254100225020b807ae09a53168dff0f138ffe59948b3448c8d900e3f238007f3fc2fa6496df3dbf4a09a535112a080165403d7041b80654c5a37fc6e00f0d8308040f5f87352398ac5754dfdad9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Thu, 11 Oct 2012 10:08:24 GMT" + }, + { + "etag": "\"3fc-4cbc5c069d600\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Thu, 24 Oct 2013 08:21:03 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1020" + }, + { + "content-type": "image/png" + }, + { + "date": "Sat, 03 Nov 2012 13:37:18 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 25, + "wire": "88c6c00f138efe6322cd1232364038fc8e001fcfc4588ca47e561cc58190b6cb80003f6496df3dbf4a09a535112a080165403d7040b8dbea62d1bfc9e30f0d03313838c0dcdb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Thu, 11 Oct 2012 10:08:24 GMT" + }, + { + "etag": "\"bc-4cbc5c069d600\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Thu, 24 Oct 2013 08:20:59 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "188" + }, + { + "content-type": "image/png" + }, + { + "date": "Sat, 03 Nov 2012 13:37:18 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 26, + "wire": "88c86c96df3dbf4a09d53716b50400894082e05bb8d854c5a37f0f138ffe44fb2b34418c8cbed3ad32407f3fc7c06496df3dbf4a019535112a080165403f71b0dc69c53168dfcbe5dade0f0d03363539dd", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Thu, 27 Sep 2012 10:15:51 GMT" + }, + { + "etag": "\"293-4caac394743c0\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Thu, 03 Oct 2013 09:51:46 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "date": "Sat, 03 Nov 2012 13:37:18 GMT" + }, + { + "content-length": "659" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 27, + "wire": "88ca0f138ffe4212acd1063232fb4eb4c901fcff6496df3dbf4a019535112a080165403f71b0dc69b53168dfc2ccc0c9e60f0d03323836dbdfde", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "etag": "\"11e-4caac394743c0\"" + }, + { + "expires": "Thu, 03 Oct 2013 09:51:45 GMT" + }, + { + "cache-control": "max-age=31536000" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Thu, 27 Sep 2012 10:15:51 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "286" + }, + { + "content-type": "image/gif" + }, + { + "date": "Sat, 03 Nov 2012 13:37:18 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 28, + "wire": "88cb0f138ffe44dc8b34418c8cbed3ad32407f3f6496df3dbf4a019535112a080165403f71b0dc6dc53168dfc3cdc1cae70f0d03363035dce0df", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "etag": "\"25d-4caac394743c0\"" + }, + { + "expires": "Thu, 03 Oct 2013 09:51:56 GMT" + }, + { + "cache-control": "max-age=31536000" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Thu, 27 Sep 2012 10:15:51 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "605" + }, + { + "content-type": "image/gif" + }, + { + "date": "Sat, 03 Nov 2012 13:37:18 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 29, + "wire": "88cc6c96e4593e94642a6a225410022502edc0bf7191298b46ff0f1390fe4220cad2cd1246ca18c2ec61003f9fcb588ca47e561cc58190b4e81969ff6496df3dbf4a321535112a08016540bb702fdc644a62d1bfd0ea0f0d84081d6c1f5f86497ca582211fe4e3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Wed, 31 Oct 2012 17:19:32 GMT" + }, + { + "etag": "\"121f4-4cd5e1b17b100\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31470349" + }, + { + "expires": "Thu, 31 Oct 2013 17:19:32 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "10750" + }, + { + "content-type": "text/css" + }, + { + "date": "Sat, 03 Nov 2012 13:37:18 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 30, + "wire": "88e25f911d75d0620d263d4c795ba0fb8d04b0d5a70f0d83132ebdece2", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "2378" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:19 GMT" + } + ] + }, + { + "seqno": 31, + "wire": "885f88352398ac74acb37f6c96c361be94134a65b6a50400854082e05ab8cbea62d1bf6196c361be940094d27eea0801128215c6deb8db2a62d1bf6496dc34fd280654d27eea0801128215c6deb8db2a62d1bff2e10f0d840880cb5fee55846c4e81dfe0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Fri, 24 Jun 2011 10:14:39 GMT" + }, + { + "date": "Fri, 02 Nov 2012 22:58:53 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 22:58:53 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "12034" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "52707" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 32, + "wire": "88e8c30f0d03333038f16196dc34fd280654d27eea0801128166e32edc1014c5a37f", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "308" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + } + ] + }, + { + "seqno": 33, + "wire": "88e9c40f0d03333038f2be", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "308" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + } + ] + }, + { + "seqno": 34, + "wire": "48826402bf768586b19272ff6495dc34fd2800a994752820000a0017000b800298b46f4085aec1cd48ff86a8eb10649cbf5886a8eb10649cbf7f22caacf4189eac2cb07f33a535dc618f1e3c2f5164424695c87a58f0c918ad9ad7f34d1fcfd297b5c1fce9d5914bfbb5a97b56d534e4bea6bdd0a90dfd0a6ae1b54c9a6fa9a61e2a5ed5a3f90f28bf40606c0fb61c0103ad5f68026dbfb50be6b3585441be7b7e940096d27eeb08017540b371976e080a62d1bfed4ac699e063ed490f48cd540bc7191721d7b7af0f1fffa3019d29aee30c206bc7191721d7b7ab11c646238c8c23fca8749609cf4957ac7317e2a44548a0f4547c54889054a0c9293ac0d81f6c3802075abed004db7f1314f1164324c7aa03549f8ac744561ed496090b28eda13f14d11543a4b0463b282a3b5a5f81d75c49f55960f058fe281d535a398b016a5b15df8a688bb96c418f54005c2d2e2f8ac7445e0b18ebae0f1e273d25ac7317e238c9152482a3a624153f0825852d5158541e8b5263d5005971cf2eb8f7c47476891032bb7f11d1da2b206576fe23a3b45de090b28eda12b783d9449e0d217e2a44512600b2d85f69f7997de71bf8a911120e1bf0acf7c548892682eddbca880b2a20633d25ac7317e2a445d1158e62db65104e94d6ab30b0c78f1e178e322e43af6f563e2a44561652d9616c830f0d033635367f31842507417f5f95497ca589d34d1f6a1271d882a60320eb3cf36fac1f", + "headers": [ + { + ":status": "302" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 01 Jan 2000 00:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.nedstat.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND NAV COM\"" + }, + { + "set-cookie": "s1=50951E1074D40255; expires=Thu, 02-Nov-2017 13:37:20 GMT; path=/; domain=.bbc.co.uk" + }, + { + "location": "http://sa.bbc.co.uk/bbc/bbc/s?name=home.page&ns_m2=yes&ns_setsiteck=50951E1074D40255&geo_edition=int&pal_route=default&ml_name=barlesque&app_type=web&language=en-GB&ml_version=0.14.2&pal_webapp=wwhomepage&bbc_mc=not_set&screen_resolution=1366x768&blq_s=3.5&blq_r=3.5&blq_v=default-worldwide&ns__t=1351949839865&ns_c=UTF-8&ns_ti=BBC%20-%20Homepage&ns_jspageurl=http%3A//www.bbc.co.uk/&ns_referrer=" + }, + { + "content-length": "656" + }, + { + "connection": "close" + }, + { + "content-type": "text/html; charset=iso-8859-1" + } + ] + }, + { + "seqno": 35, + "wire": "88dfd00f138ffe5c03d2acd1246ca18c2ec61003f9dd588ca47e561cc58190b4e819685fcfe1fb0f0d83702d83dbc7f3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Wed, 31 Oct 2012 17:19:32 GMT" + }, + { + "etag": "\"608f-4cd5e1b17b100\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31470342" + }, + { + "expires": "Thu, 31 Oct 2013 17:19:32 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "6150" + }, + { + "content-type": "application/javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 36, + "wire": "88e0da0f138ffe442656689191b201c7e47000fe7fded76496df3dbf4a09a535112a080165403d7040b8dbaa62d1bfe2fc0f0d03353437d9f5f4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Thu, 11 Oct 2012 10:08:24 GMT" + }, + { + "etag": "\"223-4cbc5c069d600\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Thu, 24 Oct 2013 08:20:57 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "547" + }, + { + "content-type": "image/png" + }, + { + "date": "Sat, 03 Nov 2012 13:37:18 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 37, + "wire": "88f3ce0f0d03333038fcc8", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "308" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + } + ] + }, + { + "seqno": 38, + "wire": "88e1db0f138ffe5e7e559a24646c8071f91c003f9fdfd86496df3dbf4a09a535112a080165403d7040b8d854c5a37fe3fd0f0d831080efdac9f5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Thu, 11 Oct 2012 10:08:24 GMT" + }, + { + "etag": "\"89f-4cbc5c069d600\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Thu, 24 Oct 2013 08:20:51 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "2207" + }, + { + "content-type": "image/png" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 39, + "wire": "88c9c7c6c5c4c30f0d023433c2f2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 01 Jan 2000 00:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.nedstat.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND NAV COM\"" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 40, + "wire": "88f4cf0f0d03333038fdc9", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "308" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + } + ] + }, + { + "seqno": 41, + "wire": "88e26c96d07abe9413ea6a225410022502cdc645700f298b46ff0f1390fe421904159a248c8a3108607000fe7fe1da6496df3dbf4a321535112a080165403d71a7ae040a62d1bfe55a839bd9ab0f0d84134279afe0f9f8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Mon, 29 Oct 2012 13:32:08 GMT" + }, + { + "etag": "\"11d21-4cd32b22a0600\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Thu, 31 Oct 2013 08:48:10 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "24284" + }, + { + "content-type": "application/javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:37:18 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 42, + "wire": "88f7d20f0d83644f87becc", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "3291" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + } + ] + }, + { + "seqno": 43, + "wire": "885f8b497ca58e83ee3412c3569f6c96e4593e940bea6e2d6a080112817ae09bb8d3ea62d1bf6196c361be940094d27eea0801128205c0b5702153168dff6496dc34fd280654d27eea0801128205c0b5702153168dff4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cbf60f0d830b6f07408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f5584704dbcfff6edc5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/javascript" + }, + { + "last-modified": "Wed, 19 Sep 2012 18:25:49 GMT" + }, + { + "date": "Fri, 02 Nov 2012 20:14:11 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 20:14:11 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "1581" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "62589" + }, + { + "cache-control": "public, max-age=86400" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 44, + "wire": "88ec6c96e4593e94642a6a225410022502edc0bf704e298b46ff0f138ffe431bb2acd1246ca11c64132f03f9eb588ca47e561cc58190b4e819683f6496df3dbf4a321535112a08016540bb702fdc138a62d1bff0c80f0d83740cbfe7d67f1088ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Wed, 31 Oct 2012 17:19:26 GMT" + }, + { + "etag": "\"1b7f-4cd5e1abc2380\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31470341" + }, + { + "expires": "Thu, 31 Oct 2013 17:19:26 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "7039" + }, + { + "content-type": "image/png" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 45, + "wire": "88f0c10f138ffe4401b2b34491b28471904cbc0fe7ee588ca47e561cc58190b4e8196dcfc0f2ca0f0d837996dbddd8bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Wed, 31 Oct 2012 17:19:26 GMT" + }, + { + "etag": "\"20a3-4cd5e1abc2380\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31470356" + }, + { + "expires": "Thu, 31 Oct 2013 17:19:26 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "8355" + }, + { + "content-type": "image/jpeg" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 46, + "wire": "887689bf7b3e65a193777b3fdf0f0d03333038cbd9", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "308" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + } + ] + }, + { + "seqno": 47, + "wire": "88f2c30f138ffe59032f2cd1246ca11c64132f03f9f0588ca47e561cc58190b4e81965bfc2f4ccebda0f0d84089969afc1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Wed, 31 Oct 2012 17:19:26 GMT" + }, + { + "etag": "\"3038-4cd5e1abc2380\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31470335" + }, + { + "expires": "Thu, 31 Oct 2013 17:19:26 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/png" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "content-length": "12344" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 48, + "wire": "88f4cccb6c96c361be9403aa6e2d6a0801128172e34e5c684a62d1bf6196dc34fd280654d27eea0801128105c13b700e298b46ff6496dd6d5f4a01a5349fba820044a041704edc038a62d1bfca768344b2970f0d84132f859fca5584085a0b5f5890aed8e8313e94a47e561cc581e71a003f", + "headers": [ + { + ":status": "200" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/javascript" + }, + { + "last-modified": "Fri, 07 Sep 2012 16:46:42 GMT" + }, + { + "date": "Sat, 03 Nov 2012 10:27:06 GMT" + }, + { + "expires": "Sun, 04 Nov 2012 10:27:06 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "23913" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "11414" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 49, + "wire": "88f9ca0f138ffe432ca059a248d94238c8265e07f3f7c6c8fad20f0d83782eb9f1e0c7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Wed, 31 Oct 2012 17:19:26 GMT" + }, + { + "etag": "\"1ff0-4cd5e1abc2380\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31470356" + }, + { + "expires": "Thu, 31 Oct 2013 17:19:26 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "8176" + }, + { + "content-type": "image/png" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 50, + "wire": "88c5e60f0d03333038d26196dc34fd280654d27eea0801128166e32edc1054c5a37f", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "308" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:21 GMT" + } + ] + }, + { + "seqno": 51, + "wire": "88fa588ca47e561cc581c640e880007fe76496d07abe94032a693f750400b4a01cb8db5700d298b46f54012a6c96dc34fd280654d27eea0801128072e360b8d32a62d1bf0f0d8365e083e5cc", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "cache-control": "max-age=63072000" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Mon, 03 Nov 2014 06:54:04 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Sat, 03 Nov 2012 06:50:43 GMT" + }, + { + "content-length": "3810" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 52, + "wire": "88fec1ea6496c361be94642a6a22541002d2820dc6dcb800298b46ffc06c96e4593e94642a6a2254100225041b8d86e36fa98b46ff0f0d836c0d3fe7ce", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "cache-control": "max-age=63072000" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Fri, 31 Oct 2014 21:56:00 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Wed, 31 Oct 2012 21:51:59 GMT" + }, + { + "content-length": "5049" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 53, + "wire": "88769086b19272b025c4b85f53fae151bcff7fc4ed6496d07abe94032a693f750400b4a01fb8d3b702ca98b46fc36c96dc34fd280654d27eea080112807ee321b82694c5a37f0f0d836c0e87ead1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "cache-control": "max-age=63072000" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Mon, 03 Nov 2014 09:47:13 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Sat, 03 Nov 2012 09:31:24 GMT" + }, + { + "content-length": "5071" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 54, + "wire": "887b8b84842d695b05443c86aa6fddf06c96df3dbf4a05f532db42820044a08571a7ee34ea98b46f6196c361be940094d27eea0801128215c03371b6d4c5a37f6496dc34fd280654d27eea0801128215c03371b6d4c5a37fdbce0f0d84642fb2e7da55846dc001cfcd", + "headers": [ + { + ":status": "200" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Thu, 19 Jul 2012 22:49:47 GMT" + }, + { + "date": "Fri, 02 Nov 2012 22:03:55 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 22:03:55 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "31936" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "56006" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 55, + "wire": "887f2afdacf4189eac2cb07f33a535dc61898e79a828e442f32f21ed8e82928313aaf5152c56398a39189895455b35c4bf9a68fe7e94bdae0fe6f70da3521bfa06a5fc1c46a6f8721d4d7ba13a9af75f3a9ab86d53269bea70d3914d7c36a9934ef52fe0d0a6edf0a9af6e052f6ad0a69878a9ab7de534eac8a5fddad4bdab6ff3cdec6496c361be940054ca3a940bef814002e001700053168dff5892a8eb10649cbf4a536a12b585ee3a0d20d25f5f87352398ac4c697fe0768320e52f0f0d023432e0", + "headers": [ + { + ":status": "200" + }, + { + "p3p": "policyref=\"http://googleads.g.doubleclick.net/pagead/gcn_p3p_.xml\", CP=\"CURa ADMa DEVa TAIo PSAo PSDo OUR IND UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "date": "Sat, 03 Nov 2012 13:37:21 GMT" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "image/gif" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "cafe" + }, + { + "content-length": "42" + }, + { + "x-xss-protection": "1; mode=block" + } + ] + }, + { + "seqno": 56, + "wire": "88cade0f138ffe59132b34491b28471904cbc0fe7f52848fd24a8f588ca47e561cc58190b4e8196c1fdec9e80f0d033830335f87352398ac5754dff7de", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Wed, 31 Oct 2012 17:19:26 GMT" + }, + { + "etag": "\"323-4cd5e1abc2380\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31470350" + }, + { + "expires": "Thu, 31 Oct 2013 17:19:26 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "803" + }, + { + "content-type": "image/png" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 57, + "wire": "88dcfd0f0d03333437e9d4", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "347" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:21 GMT" + } + ] + }, + { + "seqno": 58, + "wire": "88cd6c96e4593e94038a65b68504008540bb702d5c0054c5a37f0f1390fe4236dc2acd0dd71f8c60115e681fcfc1588ca47e561cc58190b6cb80003f6496df697e940baa6e2d6a0801654086e360b82794c5a37fcdec0f0d84640e342f5f901d75d0620d263d4c741f71a0961ab4fffbe2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Wed, 06 Jul 2011 17:14:01 GMT" + }, + { + "etag": "\"1a56e-4a769ba02e840\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Tue, 17 Sep 2013 11:50:28 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "30642" + }, + { + "content-type": "application/javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 59, + "wire": "88ceedc66c96df697e94640a6a225410022502d5c13b704fa98b46ff6196c361be940094d27eea0801128205c13f702e298b46ff6496dc34fd280654d27eea0801128205c13f702e298b46ffebde0f0d840bc071bfea5584702e3cdfdd", + "headers": [ + { + ":status": "200" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Tue, 30 Oct 2012 14:27:29 GMT" + }, + { + "date": "Fri, 02 Nov 2012 20:29:16 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 20:29:16 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "18065" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "61685" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 60, + "wire": "880f28e1b1288a1861860d19edbaf39b11f9d711aff0f0e6787c3992bc3bb6d1a7878bbbdaa30d78368387e73c5838ffb675b5f6a5f3d2335502f617ba0865ea2a7ed4c1e6b3585441badabe94032b693f758400b2a059b8cbb704153168dff6a6b1a678184088f2b5761c8b48348f89ae46568e61a00f2c1f7f0fe9acf4189eac2cb07f33a535dc618e885ec2f7410cbd454b1e19231620d5b35afe69a3f9fa52f6b83f9d3ab4a9af742a6bdd7d4c9c6153271bea6adfad4dd0e853269bea70d3914d7c36a97b568534c3c54c9a77a97f06852f69dea6edf0a9af6e05356fbca63c10ff3f76035253495886a8eb10649cbf4085aec1cd48ff86a8eb10649cbf6496df3dbf4a002a651d4a05f740a0017000b800298b46ff5f9a1d75d0620d263d4c741f71a0961ab4fd9271d882a60e1bf0acf7798624f6d5d4b27ff9da6196dc34fd280654d27eea0801128166e32edc1014c5a37f", + "headers": [ + { + ":status": "200" + }, + { + "set-cookie": "rts_AAAA=MLuB86QsXkGiDUw6LAw6IpFSRlNUwBT4lFpER0UXYGEV+3P4; Domain=.revsci.net; Expires=Sun, 03-Nov-2013 13:37:21 GMT; Path=/" + }, + { + "x-proc-data": "pd3-bgas08-1" + }, + { + "p3p": "policyref=\"http://js.revsci.net/w3c/rsip3p.xml\", CP=\"NON PSA PSD IVA IVD OTP SAM IND UNI PUR COM NAV INT DEM CNT STA PRE OTC HEA\"" + }, + { + "server": "RSI" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "content-type": "application/javascript;charset=UTF-8" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + } + ] + }, + { + "seqno": 61, + "wire": "88dee45f88352398ac74acb37f6496d07abe94032a693f750400b4a045700cdc65a53168dfe46c96dc34fd280654d27eea080112810dc00ae01f53168dff0f0d840b2eb4e7c1f2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "cache-control": "max-age=63072000" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Mon, 03 Nov 2014 12:03:34 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Sat, 03 Nov 2012 11:02:09 GMT" + }, + { + "content-length": "13746" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 62, + "wire": "88e1f50f138ffe42cb8b34491b28471904cbc0fe7fd4588ca47e561cc58190b4e804fb5ff4df5a839bd9abd4ea0f0d826420f4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Wed, 31 Oct 2012 17:19:26 GMT" + }, + { + "etag": "\"136-4cd5e1abc2380\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31470294" + }, + { + "expires": "Thu, 31 Oct 2013 17:19:26 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/png" + }, + { + "date": "Sat, 03 Nov 2012 13:37:21 GMT" + }, + { + "content-length": "310" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 63, + "wire": "88e3e9c26496d07abe94032a693f750400b4a01eb8076e09a53168dfe86c96dc34fd280654d27eea080112807ae01ab80654c5a37f0f0d84081d007fc5f6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "cache-control": "max-age=63072000" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Mon, 03 Nov 2014 08:07:24 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Sat, 03 Nov 2012 08:04:03 GMT" + }, + { + "content-length": "10701" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 64, + "wire": "88768586b19272ffecc56496dd6d5f4a004a693f750400b4a04371a72e32053168dfeb6c96c361be940094d27eea080112810dc65db82654c5a37f0f0d8369975beff9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "cache-control": "max-age=63072000" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Sun, 02 Nov 2014 11:46:30 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:37:23 GMT" + }, + { + "content-length": "4375" + }, + { + "date": "Sat, 03 Nov 2012 13:37:21 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 65, + "wire": "88e8eec76496dd6d5f4a004a693f750400b4a019b816ae09c53168dfed6c96c361be940094d27eea0801128066e041700da98b46ff0f0d8365f71bf1fb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "cache-control": "max-age=63072000" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Sun, 02 Nov 2014 03:14:26 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Fri, 02 Nov 2012 03:10:05 GMT" + }, + { + "content-length": "3965" + }, + { + "date": "Sat, 03 Nov 2012 13:37:21 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 66, + "wire": "88eaf0c96496dd6d5f4a004a693f750400b4a04571a7ee36ca98b46fef6c96c361be940094d27eea0801128115c65fb82654c5a37f0f0d8369e69af3408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "cache-control": "max-age=63072000" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Sun, 02 Nov 2014 12:49:53 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Fri, 02 Nov 2012 12:39:23 GMT" + }, + { + "content-length": "4844" + }, + { + "date": "Sat, 03 Nov 2012 13:37:21 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 67, + "wire": "88ed6c96c361be940094d27eea0801128166e099b810298b46ffe1588ca47e561cc58190b4f81e7dff6496dc34fd280129a4fdd41002ca8166e09eb8c814c5a37f0f0d840bcfb4ffcfd0c1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Fri, 02 Nov 2012 13:23:10 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31490899" + }, + { + "expires": "Sat, 02 Nov 2013 13:28:30 GMT" + }, + { + "content-length": "18949" + }, + { + "content-type": "image/jpeg" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 68, + "wire": "88f06c96c361be940094d27eea0801128176e003702fa98b46ffe4e06496dd6d5f4a0195349fba8200595002b8005c0854c5a37f0f0d84136079bfd1d2c3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Fri, 02 Nov 2012 17:01:19 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Sun, 03 Nov 2013 02:00:11 GMT" + }, + { + "content-length": "25085" + }, + { + "content-type": "image/jpeg" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 69, + "wire": "88ca6c96c361be940094d27eea080112810dc641704da98b46ffe6d2588ca47e561cc58190b4fb2d099f6496dc34fd280129a4fdd41002ca816ae04171b794c5a37f0f0d8413afb2f7d5c6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:30:25 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=31493423" + }, + { + "expires": "Sat, 02 Nov 2013 14:10:58 GMT" + }, + { + "content-length": "27938" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 70, + "wire": "88f5588ca47e561cc581c640e880007fd56496dd6d5f4a004a693f750400b4a01bb8d3f702ea98b46f54012a6c96c361be940094d27eea0801128015c699b8d38a62d1bf0f0d836db71f6196dc34fd280654d27eea0801128166e32edc1054c5a37fcb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "cache-control": "max-age=63072000" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Sun, 02 Nov 2014 05:49:17 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Fri, 02 Nov 2012 02:43:46 GMT" + }, + { + "content-length": "5569" + }, + { + "date": "Sat, 03 Nov 2012 13:37:21 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 71, + "wire": "488264026196dc34fd280654d27eea0801128166e32edc1094c5a37f768dd54d464db6154bf79812e05c1fc30f28da445dcd07feef6effe770ffc13cd42f6103e06c2e02fbd75670000003086f00207af5f6bff77b07ff3ed4c1e6b3585441be7b7e94504a693f750400baa059b8cbb704253168dff6a5f3d233550206bc7191721e9fb5358d33c0c70f1fff87049d29aee30c206bc7191721e962361086238c9e26a0f18e8aec3c8c058c6b884b8584004e3cf01c6c0fb8eb3fe43b2ec01f8ac84b204d9697e3b9a4aa013cd42f6103e06c2e02fbd75670000003086f00207af5f6be3e2a927803f09819545842054584400895101f5598597556611055101c54401340f8ef4a606b0bf0bacbf7be3bd32c11c645c2112e23babd454fc10b070df8567be09257033f158e62e91d258238c8a880abb79510273d25ac7317e268274a6b55985516154587c78f0bc7191721d7b7aaa2c3f04241c375ff824f04e7a4b58e62fc17b96a4a202f72d4917c4e18238c8abb7a73d25ac7317e3b8a0beab37eb1cc5d23a4bf046e0c9a6fe0fcf8eedc17d566f91bf823904e7a4b58e62fc77720beab37c8e7c102181034db6483f4abb782ab30b20ae9f8205b815161f8ee16e0beab37c816fe0817608e322a202aede54409cf496b1cc5f8ee1760beab37c8177e08c860c7bf467f8eec860beab37c8c87e08c8a0d274aa200fb8cd40e3a0bf1dd91417d566f91917c7769f82f9ac2912a8819ce393e08db3078f1e178e322e43af6f5f8eedb305f559be46d9f8236d413a535aacc2a8b0aa2c3e3c785e38c8b90ebdbd7e3bb6d417d566f91b6be08db7047191721e9f8eedb705f559be46dbf8236e417d566fd6398ba47497e3bb6e417d566f91b73e08dbb07a2a3e3bb6ec17d566f91b77e08e0a0f15d6abb7a892355dbd481e55dbd48a855dbd4b8655dbd486555dbd4d76aaedea44faaedea5a4aaedea5e07c7770505f559be4705f08802cb8e7975c7be09009af8e9005777e3bc1cfe3ac1cfe23f103efb5f11cf038d3ff15c1947dc6a8810d75d054aa206ba2d996354ab37765a6275de6a4aa881ae8b6658d52a203abbab85566efc43b30401f4003782d6386a50b34bb4bbf6496c361be940094d27eea0801128166e32edc1094c5a37f6c96dd6d5f4a01a5349fba820044a059b8cbb704253168df58a6a8eb10649cbf4a54759093d85fa5291f9587316007d2951d64d83a9129eca7e94aec3771a4bfe57f29bbacf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725f535ee854d5c36a9934df52f6ad0a69878a9bb7c3fcff4086f282d9dcb67f85f1e3c3816b0f0d01304088ea52d6b0e83772ff8749a929ed4c016f7f1688cc52d6b4341bb97f5f87497ca58ae819aa", + "headers": [ + { + ":status": "302" + }, + { + "date": "Sat, 03 Nov 2012 13:37:22 GMT" + }, + { + "server": "Omniture DC/2.0.0" + }, + { + "access-control-allow-origin": "*" + }, + { + "set-cookie": "s_vi=[CS]v1|284A8F0905160D8B-600001A1C0108CD4[CE]; Expires=Thu, 2 Nov 2017 13:37:22 GMT; Domain=sa.bbc.com; Path=/" + }, + { + "location": "http://sa.bbc.com/b/ss/bbcwglobalprod/1/H.22.1/s0268806509673?AQB=1&pccr=true&vidn=284A8F0905160D8B-600001A1C0108CD4&&ndh=1&t=3%2F10%2F2012%209%3A37%3A21%206%20240&vmt=4F9A739C&vmf=bbc.112.2o7.net&ce=UTF-8&cdp=3&pageName=bbc%20%7C%20homepage&g=http%3A%2F%2Fwww.bbc.co.uk%2F&cc=USD&ch=homepage&events=event2&h1=bbc%7Chomepage&v2=D%3DpageName&c5=INDEX&v5=D%3Dc5&c6=homepage&v6=D%3Dc6&c11=saturday%7C1%3A30pm&c15=%2F&v15=D%3Dc15&c17=bbc%20%7C%20homepage&v17=D%3Dc17&c31=HTML&v31=D%3Dc31&c32=Not%20available&v32=D%3Dc32&v49=Direct%20Load&c53=www.bbc.co.uk&v53=D%3Dc53&c54=http%3A%2F%2Fwww.bbc.co.uk&v54=D%3Dc54&c55=bbc.com&v55=D%3Dc55&c56=D%3DpageName&v56=D%3Dc56&c57=yes&v57=D%3Dc57&c62=wpp%7Cldb%7Cm08%7Cm2l%7Cm6i%7Cm1f%7Cmpu%7Cm29%7Cm4t%7Cm80&v62=D%3Dc62&s=1366x768&c=24&j=1.7&v=Y&k=Y&bw=994&bh=649&p=Java%20Applet%20Plug-in%3BQuickTime%20Plug-in%207.7.1%3B&AQE=1" + }, + { + "x-c": "ms-4.4.9" + }, + { + "expires": "Fri, 02 Nov 2012 13:37:22 GMT" + }, + { + "last-modified": "Sun, 04 Nov 2012 13:37:22 GMT" + }, + { + "cache-control": "no-cache, no-store, max-age=0, no-transform, private" + }, + { + "pragma": "no-cache" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA OUR IND COM NAV STA\"" + }, + { + "xserver": "www614" + }, + { + "content-length": "0" + }, + { + "keep-alive": "timeout=15" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "text/plain" + } + ] + }, + { + "seqno": 72, + "wire": "88769086b19272b025c4b85f53fae151bcff7f588aa47e561cc581e71a003fe76496dd6d5f4a01a5349fba820044a01fb8015c6dc53168dfcf6c96c361be940094d27eea080112807ee34e5c69f53168df0f0d83101a0fcedb0f1390fe48f086b34491e00198e395a681fcff52848fd24a8f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "cache-control": "max-age=86400" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Sun, 04 Nov 2012 09:02:56 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Fri, 02 Nov 2012 09:46:49 GMT" + }, + { + "content-length": "2041" + }, + { + "date": "Sat, 03 Nov 2012 13:37:21 GMT" + }, + { + "connection": "keep-alive" + }, + { + "etag": "\"c82a-4cd8003bbf440\"" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 73, + "wire": "88c2c1ea6497dd6d5f4a01a5349fba820044a01fb8cb5719694c5a37ffd26c96c361be940094d27eea080112810dc659b80754c5a37f0f0d836c2eb3d1de0f1390fe5920db6d668923c17652b4f0880fe7c0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "cache-control": "max-age=86400" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Sun, 04 Nov 2012 09:34:34 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:33:07 GMT" + }, + { + "content-length": "5173" + }, + { + "date": "Sat, 03 Nov 2012 13:37:21 GMT" + }, + { + "connection": "keep-alive" + }, + { + "etag": "\"3ca55-4cd817fe482c0\"" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 74, + "wire": "88cfced30f28da445dcd07feef6effe770ffc13cd42f6103e06c2e02fbd75670000003086f00207af5f6bff77b07ff3ed4c1e6b3585441be7b7e94504a693f750400baa059b8cbb704253168dff6a5f3d233550206bc7191721e9fb5358d33c0c70f1fff87049d29aee30c206bc7191721e962361086238c9e26a0f18e8aec3c8c058c6b884b8584004e3cf01c6c0fb8eb3fe43b2ec01f8ac84b204d9697e3b9a4aa013cd42f6103e06c2e02fbd75670000003086f00207af5f6be3e2a927803f09819545842054584400895101f5598597556611055101c54401340f8ef4a606b0bf0bacbf7be3bd32c11c645c2112e23babd454fc10b070df8567be09257033f158e62e91d258238c8a880abb79510273d25ac7317e268274a6b55985516154587c78f0bc7191721d7b7aaa2c3f04241c375ff824f04e7a4b58e62fc17b96a4a202f72d4917c4e18238c8abb7a73d25ac7317e3b8a0beab37eb1cc5d23a4bf046e0c9a6fe0fcf8eedc17d566f91bf823904e7a4b58e62fc77720beab37c8e7c102181034db6483f4abb782ab30b20ae9f8205b815161f8ee16e0beab37c816fe0817608e322a202aede54409cf496b1cc5f8ee1760beab37c8177e08c860c7bf467f8eec860beab37c8c87e08c8a0d274aa200fb8cd40e3a0bf1dd91417d566f91917c7769f82f9ac2912a8819ce393e08db3078f1e178e322e43af6f5f8eedb305f559be46d9f8236d413a535aacc2a8b0aa2c3e3c785e38c8b90ebdbd7e3bb6d417d566f91b6be08db7047191721e9f8eedb705f559be46dbf8236e417d566fd6398ba47497e3bb6e417d566f91b73e08dbb07a2a3e3bb6ec17d566f91b77e08e0a0f15d6abb7a892355dbd481e55dbd48a855dbd4b8655dbd486555dbd4d76aaedea44faaedea5a4aaedea5e07c7770505f559be4705f08802cb8e7975c7be09009af8e9005777e3bc1cfe3ac1cfe23f103efb5f11cf038d3ff15c1947dc6a8810d75d054aa206ba2d996354ab37765a6275de6a4aa881ae8b6658d52a203abbab85566efc43b30401fcdcccbcaf1c97f0985f1e3c382070f0d023433c8c75f87352398ac4c697f0f1393fe5b03ed870044b3785e6566d96c0dde05dfe77b012a", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:37:22 GMT" + }, + { + "server": "Omniture DC/2.0.0" + }, + { + "access-control-allow-origin": "*" + }, + { + "set-cookie": "s_vi=[CS]v1|284A8F0905160D8B-600001A1C0108CD4[CE]; Expires=Thu, 2 Nov 2017 13:37:22 GMT; Domain=sa.bbc.com; Path=/" + }, + { + "location": "http://sa.bbc.com/b/ss/bbcwglobalprod/1/H.22.1/s0268806509673?AQB=1&pccr=true&vidn=284A8F0905160D8B-600001A1C0108CD4&&ndh=1&t=3%2F10%2F2012%209%3A37%3A21%206%20240&vmt=4F9A739C&vmf=bbc.112.2o7.net&ce=UTF-8&cdp=3&pageName=bbc%20%7C%20homepage&g=http%3A%2F%2Fwww.bbc.co.uk%2F&cc=USD&ch=homepage&events=event2&h1=bbc%7Chomepage&v2=D%3DpageName&c5=INDEX&v5=D%3Dc5&c6=homepage&v6=D%3Dc6&c11=saturday%7C1%3A30pm&c15=%2F&v15=D%3Dc15&c17=bbc%20%7C%20homepage&v17=D%3Dc17&c31=HTML&v31=D%3Dc31&c32=Not%20available&v32=D%3Dc32&v49=Direct%20Load&c53=www.bbc.co.uk&v53=D%3Dc53&c54=http%3A%2F%2Fwww.bbc.co.uk&v54=D%3Dc54&c55=bbc.com&v55=D%3Dc55&c56=D%3DpageName&v56=D%3Dc56&c57=yes&v57=D%3Dc57&c62=wpp%7Cldb%7Cm08%7Cm2l%7Cm6i%7Cm1f%7Cmpu%7Cm29%7Cm4t%7Cm80&v62=D%3Dc62&s=1366x768&c=24&j=1.7&v=Y&k=Y&bw=994&bh=649&p=Java%20Applet%20Plug-in%3BQuickTime%20Plug-in%207.7.1%3B&AQE=1" + }, + { + "x-c": "ms-4.4.9" + }, + { + "expires": "Fri, 02 Nov 2012 13:37:22 GMT" + }, + { + "last-modified": "Sun, 04 Nov 2012 13:37:22 GMT" + }, + { + "cache-control": "no-cache, no-store, max-age=0, no-transform, private" + }, + { + "pragma": "no-cache" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA OUR IND COM NAV STA\"" + }, + { + "xserver": "www620" + }, + { + "content-length": "43" + }, + { + "keep-alive": "timeout=15" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "image/gif" + }, + { + "etag": "\"50951E12-5F83-53505C0B\"" + }, + { + "vary": "*" + } + ] + }, + { + "seqno": 75, + "wire": "88c76c96c361be940094d27eea0801128105c1357197d4c5a37fc4588ca47e561cc58190b4f01969ff6496dc34fd280129a4fdd41002ca8105c64571a0298b46ff0f0d8465a7dc67f2f3e4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Fri, 02 Nov 2012 10:24:39 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31480349" + }, + { + "expires": "Sat, 02 Nov 2013 10:32:40 GMT" + }, + { + "content-length": "34963" + }, + { + "content-type": "image/jpeg" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 76, + "wire": "88ca6c96df697e94132a6a225410022502fdc6c371a714c5a37fc7ca6496dd6d5f4a01a5349fba820044a019b8d8ae09d53168df0f0d840b8cb2dff4d9e60f1390fe5a704ecab3442472b4420dd79e07f3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Tue, 23 Oct 2012 19:51:46 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=86400" + }, + { + "expires": "Sun, 04 Nov 2012 03:52:27 GMT" + }, + { + "content-length": "16335" + }, + { + "content-type": "image/jpeg" + }, + { + "date": "Sat, 03 Nov 2012 13:37:21 GMT" + }, + { + "connection": "keep-alive" + }, + { + "etag": "\"4627f-4ccbf4cca7880\"" + } + ] + }, + { + "seqno": 77, + "wire": "885f911d75d0620d263d4c795ba0fb8d04b0d5a77b8b84842d695b05443c86aa6ff26496dc34fd281754d27eea0801128166e32edc1054c5a37fdc0f0d83085a07e9589caec3771a4bf4a54759360ea44a7b29fa5291f958731600880fb8007f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Sat, 17 Nov 2012 13:37:21 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:21 GMT" + }, + { + "content-length": "1140" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-transform, max-age=1209600" + } + ] + }, + { + "seqno": 78, + "wire": "88da6c96d07abe9403ea65b68504008940b97000b820298b46ff0f138ffe4216c4d01665e5a36e508a4003f9cd0f0d023536588ba47e561cc581d75d7000076496dd6d5f4a01d535112a0801128176e32ddc69a53168df7f0d85f1e3c34e375f901d75d0620d263d4c741f71a0961ab4ffe0ef", + "headers": [ + { + ":status": "200" + }, + { + "server": "Omniture DC/2.0.0" + }, + { + "last-modified": "Mon, 09 Jul 2012 16:00:20 GMT" + }, + { + "etag": "\"115240-38-b5f12d00\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "56" + }, + { + "cache-control": "max-age=7776000" + }, + { + "expires": "Sun, 07 Oct 2012 17:35:44 GMT" + }, + { + "xserver": "www465" + }, + { + "content-type": "application/javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:37:22 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 79, + "wire": "88f66c96c361be940094d27eea0801128166e09bb8d054c5a37fd25f88352398ac74acb37f588ca47e561cc58190b6cb6fb4f76497dd6d5f4a0195349fba8200595001b8dbf71a654c5a37ff0f0d8468216ddf6196dc34fd280654d27eea0801128166e32edc1014c5a37ff4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Fri, 02 Nov 2012 13:25:41 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=31535948" + }, + { + "expires": "Sun, 03 Nov 2013 01:59:43 GMT" + }, + { + "content-length": "41157" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 80, + "wire": "88da6c96c361be940894d444a820044a05bb8d3f700fa98b46ffd7588ca47e561cc58190b6cb6ebee76496c361be940054d27eea0801654035700f5c6c4a62d1bf0f0d8469969f6fc4c1f7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Fri, 12 Oct 2012 15:49:09 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31535796" + }, + { + "expires": "Fri, 01 Nov 2013 04:08:52 GMT" + }, + { + "content-length": "43495" + }, + { + "content-type": "image/jpeg" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 81, + "wire": "88ddeec46496d07abe94032a693f750400b4a043704fdc69a53168dfed6c96dc34fd280654d27eea080112810dc0b7704d298b46ff0f0d8479969b73c3f9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "cache-control": "max-age=63072000" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Mon, 03 Nov 2014 11:29:44 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Sat, 03 Nov 2012 11:15:24 GMT" + }, + { + "content-length": "83456" + }, + { + "date": "Sat, 03 Nov 2012 13:37:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 82, + "wire": "88df6c96df3dbf4a002a693f7504008940b371b76e32ea98b46fdcdf6496dd6d5f4a01a5349fba820044a043702cdc6c0a62d1bf0f0d840bcc89afc8eefb0f1390fe5e204122cd12472571c905238d03f9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:57:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=86400" + }, + { + "expires": "Sun, 04 Nov 2012 11:13:50 GMT" + }, + { + "content-length": "18324" + }, + { + "content-type": "image/jpeg" + }, + { + "date": "Sat, 03 Nov 2012 13:37:21 GMT" + }, + { + "connection": "keep-alive" + }, + { + "etag": "\"8c10d-4cd6f66d2d640\"" + } + ] + }, + { + "seqno": 83, + "wire": "89d2d15a839bd9ab6496d07abe940054ca3a940bef814002e001700053168dffee0f0d01307f2688ea52d6b0e83772ff58b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bf4085aec1cd48ff86a8eb10649cbf", + "headers": [ + { + ":status": "204" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:22 GMT" + }, + { + "content-length": "0" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 84, + "wire": "88e66c96df697e94640a6a225410022502ddc69ab8d36a62d1bfe3cee66496dc34fd280654d27eea080112817ae32fdc136a62d1bf0f0d8413410bdff3c20f1390fe631942facd12469e18da75f6da07f3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Tue, 30 Oct 2012 15:44:45 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=86400" + }, + { + "expires": "Sat, 03 Nov 2012 18:39:25 GMT" + }, + { + "content-length": "24118" + }, + { + "date": "Sat, 03 Nov 2012 13:37:22 GMT" + }, + { + "connection": "keep-alive" + }, + { + "etag": "\"bae19-4cd48aa479540\"" + } + ] + }, + { + "seqno": 85, + "wire": "8876a686b19272b025c4b884a7f5c2a379fed4a4f2448450c09712e2129aab2d5bb767600bbebbb27fe8d06496dd6d5f4a01a5349fba820044a01eb800dc6c0a62d1bff96c96c361be94138a6a2254100225041b826ee09b53168dff0f0d84105f7dfff6c50f1390fe5e24a37d668849492b6cc71c6d03f9e7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.22 (Unix) mod_ssl/2.2.22 OpenSSL/0.9.7d" + }, + { + "cache-control": "max-age=86400" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Sun, 04 Nov 2012 08:01:50 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Fri, 26 Oct 2012 21:25:25 GMT" + }, + { + "content-length": "21999" + }, + { + "date": "Sat, 03 Nov 2012 13:37:22 GMT" + }, + { + "connection": "keep-alive" + }, + { + "etag": "\"8cfa9-4ccfcf53bbb40\"" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 86, + "wire": "88ebead26496dd6d5f4a01a5349fba820044a003704edc65c53168dffb6c96c361be940894d444a820044a05fb8c8ae34253168dff0f0d8413e0033ff8c70f1390fe6365f79959a24650900dbed0de07f3e9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "cache-control": "max-age=86400" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Sun, 04 Nov 2012 01:27:36 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Fri, 12 Oct 2012 19:32:42 GMT" + }, + { + "content-length": "29003" + }, + { + "date": "Sat, 03 Nov 2012 13:37:22 GMT" + }, + { + "connection": "keep-alive" + }, + { + "etag": "\"b3983-4cbe1c0594a80\"" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 87, + "wire": "88edecd46496dd6d5f4a01a5349fba820044a041702d5c1094c5a37f54012a6c96df3dbf4a002a693f7504008940b371b72e36253168df0f0d8413ceb42f6196dc34fd280654d27eea0801128166e32edc1054c5a37fcb0f1390fe5f8dd20166892392b8d09a642007f3ed", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "cache-control": "max-age=86400" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Sun, 04 Nov 2012 10:14:22 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:56:52 GMT" + }, + { + "content-length": "28742" + }, + { + "date": "Sat, 03 Nov 2012 13:37:21 GMT" + }, + { + "connection": "keep-alive" + }, + { + "etag": "\"9b7c0-4cd6f64243100\"" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 88, + "wire": "88f1588ca47e561cc58190b4dba06dafd96496dc34fd280129a4fdd41002ca8115c03571a754c5a37fc26c96c361be940094d27eea080112810dc6ddb8cbca62d1bf0f0d84134d89cf6196dc34fd280654d27eea0801128166e32edc1094c5a37fcf0f1390fe5920db6d668923c17652b4f0880fe7f1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "cache-control": "max-age=31457054" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Sat, 02 Nov 2013 12:04:47 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Fri, 02 Nov 2012 11:57:38 GMT" + }, + { + "content-length": "24526" + }, + { + "date": "Sat, 03 Nov 2012 13:37:22 GMT" + }, + { + "connection": "keep-alive" + }, + { + "etag": "\"3ca55-4cd817fe482c0\"" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 89, + "wire": "88f5f4dc6496dc34fd280654d27eea0801128176e09ab8db4a62d1bfc56c96c361be940094d27eea080112816ae340b8d014c5a37f0f0d8469b038d7c4d10f1390fe6524adb4b34491e68257e518c00fe7f3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "cache-control": "max-age=86400" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Sat, 03 Nov 2012 17:24:54 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Fri, 02 Nov 2012 14:40:40 GMT" + }, + { + "content-length": "45064" + }, + { + "date": "Sat, 03 Nov 2012 13:37:21 GMT" + }, + { + "connection": "keep-alive" + }, + { + "etag": "\"fcf54-4cd841e9faa00\"" + }, + { + "accept-ranges": "bytes" + } + ] + }, + { + "seqno": 90, + "wire": "88e86c96d07abe9413ea6a225410022500cdc0bb719694c5a37f768821e8a0a4498f53df4003703370a7acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725ffe7fd65890aed8e8313e94a47e561cc581c034f0016196dc34fd280654d27eea0801128166e32edc132a62d1bf0f0d836da6c1d6ec", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Mon, 29 Oct 2012 03:17:34 GMT" + }, + { + "server": "collection8" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID\"" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "public, max-age=604800" + }, + { + "date": "Sat, 03 Nov 2012 13:37:23 GMT" + }, + { + "content-length": "5450" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 91, + "wire": "886196dc34fd280654d27eea0801128166e32edc134a62d1bf5f8b497ca58e83ee3412c3569f0f0d03313733d80f28eaef03cd3ccbc1189c7881132f04a291f7c31bd1ca391b03ed84a169b7a465f71671c65a03ccbed81f6c250b4f32d44cb2e39f6a17cd66b0a88370d3f4a0195b49fbac20044a05ab8076e09a53168dff6a5634cf031f6a487a466aa05cb2ca5224ddcb49468b6c2af5153f5886a8eb10649cbf640130768821e8a0a4498f5041408b20c9395690d614893772ff86a8eb10649cbf408caec1cd48d690d614893772ff86a8eb10649cbfdbc7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:37:24 GMT" + }, + { + "content-type": "text/javascript" + }, + { + "content-length": "173" + }, + { + "connection": "keep-alive" + }, + { + "set-cookie": "v=848381a268c12381e2d991b8bfad50951e1458d396-6634083950951e14834_3366; expires=Sat, 03-Nov-2012 14:07:24 GMT; path=/; domain=.effectivemeasure.net" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "server": "collection10" + }, + { + "cache-directive": "no-cache" + }, + { + "pragma-directive": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID\"" + } + ] + }, + { + "seqno": 92, + "wire": "88768586b19272fff46c96df697e940894cb6d0a08010a8172e00571b7d4c5a37f52848fd24a8fe20f0d03393238f7588ca47e561cc5804eb4179e7dff6496d07abe940b8a6e2d6a0801654106e36fdc038a62d1bf6196dc34fd280654d27eea0801128166e32edc13aa62d1bfe3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Tue, 12 Jul 2011 16:02:59 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "928" + }, + { + "content-type": "application/x-javascript" + }, + { + "cache-control": "max-age=27418899" + }, + { + "expires": "Mon, 16 Sep 2013 21:59:06 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:27 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 93, + "wire": "88c3f95f86497ca582211fe66c96df697e94640a6a225410022500f5c13971a754c5a37f588ca47e561cc581c13a075d71bf6496df3dbf4a320535112a080169403d704e5c6da53168df6196dc34fd280654d27eea0801128166e32edc13ea62d1bf0f0d8365a0bbe8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "text/css" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:26:47 GMT" + }, + { + "cache-control": "max-age=62707765" + }, + { + "expires": "Thu, 30 Oct 2014 08:26:54 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "content-length": "3417" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 94, + "wire": "88c87b8b84842d695b05443c86aa6fc3eb6c96df697e94640a6a225410022500f5c13b702e298b46ff0f0d8313417f588ca47e561cc581c13a075d6c3f6496df3dbf4a320535112a080169403d704e5c680a62d1bfc2ec", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "text/css" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:27:16 GMT" + }, + { + "content-length": "2419" + }, + { + "cache-control": "max-age=62707751" + }, + { + "expires": "Thu, 30 Oct 2014 08:26:40 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 95, + "wire": "88ccc1c6eec00f0d83089e0f588ca47e561cc581c13a075d799f6496df3dbf4a320535112a080169403d704edc0894c5a37fc4ee", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "text/css" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:27:16 GMT" + }, + { + "content-length": "1281" + }, + { + "cache-control": "max-age=62707783" + }, + { + "expires": "Thu, 30 Oct 2014 08:27:12 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 96, + "wire": "88cec3c8f06c96df697e94640a6a225410022500f5c13b704f298b46ff588ca47e561cc581c13a075e0b3f6496df3dbf4a320535112a080169403d704edc684a62d1bfc70f0d023536f1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "text/css" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:27:28 GMT" + }, + { + "cache-control": "max-age=62707813" + }, + { + "expires": "Thu, 30 Oct 2014 08:27:42 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "content-length": "56" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 97, + "wire": "88c7d16c96e4593e940bca693f7504003ea045702edc0094c5a37f0f0d83089d6f588aa47e561cc581969b70006496e4593e9403aa693f7504008940b371976e09f53168df7b05582d43444eea4088ea52d6b0e83772ff8e49a929ed4c0dfd2948fcc0ebaf7f7f3788cc52d6b4341bb97f5f911d75d0620d263d4c795ba0fb8d04b0d5a7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Wed, 18 Nov 2009 12:17:02 GMT" + }, + { + "content-length": "1275" + }, + { + "cache-control": "max-age=345600" + }, + { + "expires": "Wed, 07 Nov 2012 13:37:29 GMT" + }, + { + "vary": "X-CDN" + }, + { + "access-control-allow-origin": "*" + }, + { + "keep-alive": "timeout=5, max=778" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "application/x-javascript" + } + ] + }, + { + "seqno": 98, + "wire": "88d86c96c361be940894d444a820044a01fb8cb7700fa98b46ffc3eed30f0d03393431cff9408af2b10649cab0c8931eaf044d4953534088f2b10649cab0e62f0130589aaec3771a4bf4a523f2b0e62c00fa529b5095ac2f71d0690692ff4089f2b511ad51c8324e5f834d9697c6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Fri, 12 Oct 2012 09:35:09 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 13:37:29 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "text/css" + }, + { + "content-length": "941" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-action": "MISS" + }, + { + "x-cache-age": "0" + }, + { + "cache-control": "private, max-age=0, must-revalidate" + }, + { + "x-lb-nocache": "true" + }, + { + "vary": "X-CDN" + } + ] + }, + { + "seqno": 99, + "wire": "88dd6c96df697e941094d27eea08010a807ee360b8c894c5a37f4084f2b563938d1f739a4523b0fe105b148ed9bfd45a839bd9ab0f0d830b407fc6588ca47e561cc581965e0b2e81ff6496c361be9413ea693f750400b2a085702fdc0bca62d1bfd87f0a88ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Tue, 22 Nov 2011 09:50:32 GMT" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1409" + }, + { + "content-type": "application/x-javascript" + }, + { + "cache-control": "max-age=33813709" + }, + { + "expires": "Fri, 29 Nov 2013 22:19:18 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 100, + "wire": "88e36c96df697e941094d27eea08010a807ee32fdc6da53168dfc3d9c20f0d8365a65cca588ca47e561cc581965e0b4cbad76496c361be9413ea693f750400b2a08571905c132a62d1bfdcc1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Tue, 22 Nov 2011 09:39:54 GMT" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "3436" + }, + { + "content-type": "application/x-javascript" + }, + { + "cache-control": "max-age=33814374" + }, + { + "expires": "Fri, 29 Nov 2013 22:30:23 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 101, + "wire": "88e66c96df697e941094d27eea08010a807ee321b82714c5a37fcdc6dcc50f0d03333438588ca47e561cc5819640eb8d36df6496df3dbf4a082a693f750400b2a01fb8c86e34d298b46fdfc4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Tue, 22 Nov 2011 09:31:26 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "348" + }, + { + "cache-control": "max-age=33076455" + }, + { + "expires": "Thu, 21 Nov 2013 09:31:44 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 102, + "wire": "88e9decfc8c76c96df3dbf4a044a65b6850400894086e09cb821298b46ff0f0d8365c71d588ca47e561cc581b65969d682cf6496d07abe940b4a65b6850400b4a0017041b801298b46ffe2c7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "application/x-javascript" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 12 Jul 2012 11:26:22 GMT" + }, + { + "content-length": "3667" + }, + { + "cache-control": "max-age=53347413" + }, + { + "expires": "Mon, 14 Jul 2014 00:21:02 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 103, + "wire": "88ece1d2cbca6c96df3dbf4a044a65b6850400894086e09cb82754c5a37f0f0d830befbf588ca47e561cc581b644169b683f6496dc34fd28112996da141002d2810dc1397190298b46ffe5ca", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "application/x-javascript" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 12 Jul 2012 11:26:27 GMT" + }, + { + "content-length": "1999" + }, + { + "cache-control": "max-age=53214541" + }, + { + "expires": "Sat, 12 Jul 2014 11:26:30 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 104, + "wire": "48826401f05f95497ca589d34d1f6a1271d882a60320eb3cf36fac1f0f1fc39d29aee30c169ad78e3219721d7b7ab05a6b62c2d051a0a8623b69ad8b0bdcc831ea430f81b13ef305a632c8bf447f85a6b83c1eca24f0690bf05a871d05bd414764010f0d033330355888a47e561cc580410f6496dc34fd280654d27eea0801128166e341b800298b46ffe9cee8", + "headers": [ + { + ":status": "301" + }, + { + "server": "Apache" + }, + { + "content-type": "text/html; charset=iso-8859-1" + }, + { + "location": "http://emp.bbci.co.uk/emp/releases/bump/revisions/905298/embed.js?emp=worldwide&enableClear=1" + }, + { + "content-length": "305" + }, + { + "cache-control": "max-age=211" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 105, + "wire": "88f3e8edd16c96df697e94640a6a225410022500f5c13b704153168dff588ca47e561cc581c13a075d7dcf6496df3dbf4a320535112a080169403d704edc136a62d1bfec0f0d03333033d1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "text/css" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:27:21 GMT" + }, + { + "cache-control": "max-age=62707796" + }, + { + "expires": "Thu, 30 Oct 2014 08:27:25 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "content-length": "303" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 106, + "wire": "88f6ebf0d46c96df697e94640a6a225410022500f5c13b719754c5a37f0f0d8369d681588ca47e561cc581c13a075e10bf6496df3dbf4a320535112a080169403d704edc6c2a62d1bfefd4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "text/css" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:27:37 GMT" + }, + { + "content-length": "4740" + }, + { + "cache-control": "max-age=62707822" + }, + { + "expires": "Thu, 30 Oct 2014 08:27:51 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 107, + "wire": "88f96496dc34fd280654d27eea0801128166e32edc13aa62d1bf54012a5f87497ca589d34d1f0f0d847c220b9ff7d7e0dfdedde5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 03 Nov 2012 13:37:27 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "text/html" + }, + { + "content-length": "91216" + }, + { + "date": "Sat, 03 Nov 2012 13:37:27 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-action": "MISS" + }, + { + "x-cache-age": "0" + }, + { + "cache-control": "private, max-age=0, must-revalidate" + }, + { + "x-lb-nocache": "true" + }, + { + "vary": "X-CDN" + } + ] + }, + { + "seqno": 108, + "wire": "88fc6c96df697e9403ea6a225410022502edc0b5700fa98b46ffe7c0e30f0d03343436f3d8e1e0dfdee6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Tue, 09 Oct 2012 17:14:09 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 13:37:29 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "446" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-action": "MISS" + }, + { + "x-cache-age": "0" + }, + { + "cache-control": "private, max-age=0, must-revalidate" + }, + { + "x-lb-nocache": "true" + }, + { + "vary": "X-CDN" + } + ] + }, + { + "seqno": 109, + "wire": "88fd6c96e4593e9413ca65b6850400814086e01db8cb8a62d1bff3dc0f0d8379a645e4588ca47e561cc581965e0b2eba1f6496c361be9413ea693f750400b2a0857040b820298b46fff6db", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Wed, 28 Jul 2010 11:07:36 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "8432" + }, + { + "content-type": "application/x-javascript" + }, + { + "cache-control": "max-age=33813771" + }, + { + "expires": "Fri, 29 Nov 2013 22:20:20 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 110, + "wire": "88768586b19272ff6c96d07abe9403ea65b68504008940b7700fdc132a62d1bfe1f7e00f0d8310996bfc588ca47e561cc581b65969a038f76496dd6d5f4a059532db4282005a504cdc137702ea98b46ffadf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Mon, 09 Jul 2012 15:09:23 GMT" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "2234" + }, + { + "content-type": "text/css" + }, + { + "cache-control": "max-age=53344068" + }, + { + "expires": "Sun, 13 Jul 2014 23:25:17 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 111, + "wire": "88c16c96df697e94132a6a225410022500edc65eb8cbca62d1bfe4fae30f0d830b8dbb5f87352398ac4c697f588ca47e561cc581c104000b2f7f6496df3dbf4a099535112a080169403b7197ee34ea98b46f6196dc34fd280654d27eea0801128166e32edc13ea62d1bfe4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Tue, 23 Oct 2012 07:38:38 GMT" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1657" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "max-age=62100138" + }, + { + "expires": "Thu, 23 Oct 2014 07:39:47 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 112, + "wire": "88c67b8b84842d695b05443c86aa6f5f88352398ac74acb37feae96c96dc34fd280654d27eea0801128015c699b8cb2a62d1bf0f0d836da7dc588ca47e561cc581c640cb206dff6496d07abe94032a693f750400b4a00571a7ae09e53168dfc3e9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Sat, 03 Nov 2012 02:43:33 GMT" + }, + { + "content-length": "5496" + }, + { + "cache-control": "max-age=63033059" + }, + { + "expires": "Mon, 03 Nov 2014 02:48:28 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 113, + "wire": "88cbc2c6edec6c96dc34fd280654d27eea080112810dc00ae01f53168dff0f0d8369f7da588ca47e561cc581c640e09d643f6496d07abe94032a693f750400b4a043700cdc0014c5a37fc6ec", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/gif" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Sat, 03 Nov 2012 11:02:09 GMT" + }, + { + "content-length": "4994" + }, + { + "cache-control": "max-age=63062731" + }, + { + "expires": "Mon, 03 Nov 2014 11:03:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 114, + "wire": "88c6ce0f28e3bb8b864bf038d940fbedb62090a00bef91c13d205f6650b6f34ec71cb1c606590899723787189b75f2bf246c8caf36fbecca0fb50be6b3585441c8b27d28012da4fdd60b8a059b8cbb704fa98b46ffb52b1a67818fb5243d23355047191721d7b7afdf6c96e4593e940054d03b141000e2816ee059b8db8a62d1bf52848fd24a8f0f0d0234335896a47e561cc5801f4a547588324e5837152b5e39fa98bf6496dc34fd280654d27eea0801128166e32edc13ea62d1bf4088ea52d6b0e83772ff8e49a929ed4c0107d2948fcc01785f7f3288cc52d6b4341bb97fcf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "server": "Apache" + }, + { + "set-cookie": "BGUID=65e0995521ce0199c628d193f15847bbfbb0331236b8ab2579e9db3ae85993f0; expires=Wed, 02-Nov-16 13:37:29 GMT; path=/; domain=bbc.co.uk;" + }, + { + "last-modified": "Wed, 01 Mar 2006 15:13:56 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "43" + }, + { + "cache-control": "max-age=0, no-cache=Set-Cookie" + }, + { + "expires": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "keep-alive": "timeout=10, max=182" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 115, + "wire": "88d45f911d75d0620d263d4c795ba0fb8d04b0d5a70f1fc39d29aee30c169ad78e3219721d7b7ab05a6b62c2d051a0a8623b69ad8b0bdcc831ea430f81b13ef305a632c8bf447f85a6b83c1eca24f0690bf05a871d05bd414764010f0d8313827b5888a47e561cc5802e396496dc34fd280654d27eea0801128166e340b816d4c5a37fcff5ce6c96e4593e940854cb6d0a0801128266e059b8c854c5a37fc6f9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "content-type": "application/x-javascript" + }, + { + "location": "http://emp.bbci.co.uk/emp/releases/bump/revisions/905298/embed.js?emp=worldwide&enableClear=1" + }, + { + "content-length": "2628" + }, + { + "cache-control": "max-age=166" + }, + { + "expires": "Sat, 03 Nov 2012 13:40:15 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Wed, 11 Jul 2012 23:13:31 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 116, + "wire": "88d8cfcef96c96dc34fd280654d27eea0801128005c106e01953168dff588ca47e561cc581c6402699685f6496d07abe94032a693f750400b4a001704cdc0854c5a37fd30f0d8365c135f9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Sat, 03 Nov 2012 00:21:03 GMT" + }, + { + "cache-control": "max-age=63024342" + }, + { + "expires": "Mon, 03 Nov 2014 00:23:11 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "content-length": "3624" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 117, + "wire": "88dbd2d1fc6c96dc34fd280654d27eea0801128005c106e09e53168dff0f0d8369c699588ca47e561cc581c6402699103f6496d07abe94032a693f750400b4a0017042b8d3ea62d1bfd67f0988ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Sat, 03 Nov 2012 00:21:28 GMT" + }, + { + "content-length": "4643" + }, + { + "cache-control": "max-age=63024320" + }, + { + "expires": "Mon, 03 Nov 2014 00:22:49 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 118, + "wire": "88dfd65f86497ca582211f5a839bd9ab6c96df697e94640a6a225410022500fdc10ae36d298b46ff0f0d837c4eb7588ca47e561cc581c13a1081e7bf6496df3dbf4a320535112a080169403f7042b81754c5a37fdcc3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "text/css" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 09:22:54 GMT" + }, + { + "content-length": "9275" + }, + { + "cache-control": "max-age=62711088" + }, + { + "expires": "Thu, 30 Oct 2014 09:22:17 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 119, + "wire": "88e4dbcdc16c96df697e94640a6a225410022500fdc10ae36da98b46ff588ca47e561cc581c13a108597bf6496df3dbf4a320535112a080169403f704cdc03aa62d1bfdf0f0d84085f785fc6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 09:22:55 GMT" + }, + { + "cache-control": "max-age=62711138" + }, + { + "expires": "Thu, 30 Oct 2014 09:23:07 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "content-length": "11982" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 120, + "wire": "88e7dd6c96dc34fd280654d27eea080112810dc13f71b694c5a37f0f0d836d9719588ca47e561cc581c640e34d005f6496d07abe94032a693f750400b4a04371905c6c4a62d1bf6196dc34fd280654d27eea0801128166e32edc640a62d1bfca", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Sat, 03 Nov 2012 11:29:54 GMT" + }, + { + "content-length": "5363" + }, + { + "cache-control": "max-age=63064402" + }, + { + "expires": "Mon, 03 Nov 2014 11:30:52 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:30 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 121, + "wire": "88769086b19272b025c4b85f53fae151bcff7f6c96df3dbf4a042a6a2254100225020b807ae09a53168dff0f138ffe5996559a24646c8071f91c003f9fdb588ca47e561cc58190b6cb80003f6496df3dbf4a09a535112a080165403d7042b82714c5a37fe6cc0f0d830804cf5f87352398ac5754dfc3cf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Thu, 11 Oct 2012 10:08:24 GMT" + }, + { + "etag": "\"3ff-4cbc5c069d600\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Thu, 24 Oct 2013 08:22:26 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1023" + }, + { + "content-type": "image/png" + }, + { + "date": "Sat, 03 Nov 2012 13:37:30 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 122, + "wire": "88f06c96dc34fd280654d27eea0801128005c65ab8db6a62d1bf4084f2b563938d1f739a4523b0fe105b148ed9bfe9cf0f0d836dc659e8588ca47e561cc581c64026c407ff6496d07abe94032a693f750400b4a00171976e32fa98b46fc7d3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Sat, 03 Nov 2012 00:34:55 GMT" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "5633" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=63025209" + }, + { + "expires": "Mon, 03 Nov 2014 00:37:39 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:30 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 123, + "wire": "88f46c96e4593e9403aa681d8a0801128105c699b8d3aa62d1bfebc1ecd20f0d83744e3f588ca47e561cc581a644cb6e099f6496df697e940bca681d8a08016941337190dc0894c5a37fefd6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Wed, 07 Mar 2012 10:43:47 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "7269" + }, + { + "cache-control": "max-age=43235623" + }, + { + "expires": "Tue, 18 Mar 2014 23:31:12 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 124, + "wire": "88f7eed5d46c96df697e94640a6a225410022500f5c102e36d298b46ff588ca47e561cc581c13a075b135f6496df3dbf4a320535112a080169403d7042b8db2a62d1bff20f0d8465a65f6fd9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "text/css" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:20:54 GMT" + }, + { + "cache-control": "max-age=62707524" + }, + { + "expires": "Thu, 30 Oct 2014 08:22:53 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:29 GMT" + }, + { + "content-length": "34395" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 125, + "wire": "88fa6c96df697e94640a6a225410022500fdc10ae36ea98b46fff2d80f0d8413420bbfe4d36496df3dbf4a320535112a080169403f704cdc03ca62d1bfcfdb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Tue, 30 Oct 2012 09:22:57 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "24217" + }, + { + "content-type": "application/x-javascript" + }, + { + "cache-control": "max-age=62711138" + }, + { + "expires": "Thu, 30 Oct 2014 09:23:08 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:30 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 126, + "wire": "88fcf3f2c8d96c96e4593e94642a6a225410022502ddc69eb82754c5a37f0f0d8410190b9f588ca47e561cc581c640d85e79ef6496d07abe94032a693f750400b4a01eb8015c0bca62d1bfd2de", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Wed, 31 Oct 2012 15:48:27 GMT" + }, + { + "content-length": "20316" + }, + { + "cache-control": "max-age=63051888" + }, + { + "expires": "Mon, 03 Nov 2014 08:02:18 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:30 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 127, + "wire": "88768586b19272fff76c96df697e940894cb6d0a08010a816ee36fdc03ea62d1bfefde0f0d84642c841fea588ca47e561cc5804eb4179e69df6496d07abe940b8a6e2d6a0801654106e36f5c0baa62d1bfd6e2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Tue, 12 Jul 2011 15:59:09 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "31310" + }, + { + "content-type": "application/x-javascript" + }, + { + "cache-control": "max-age=27418847" + }, + { + "expires": "Mon, 16 Sep 2013 21:58:17 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:30 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 128, + "wire": "88c1fa6c96df697e940894cb6d0a08010a816ee36fdc0814c5a37ff2e10f0d84642e81efed588ca47e561cc5804eb4179d7dff6496d07abe940b8a6e2d6a0801654106e36edc642a62d1bf6196dc34fd280654d27eea0801128166e32edc644a62d1bfe6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Tue, 12 Jul 2011 15:59:10 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "31708" + }, + { + "content-type": "application/x-javascript" + }, + { + "cache-control": "max-age=27418799" + }, + { + "expires": "Mon, 16 Sep 2013 21:57:31 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:32 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 129, + "wire": "88c57b8b84842d695b05443c86aa6ff1e56c96df3dbf4a044a65b6850400894082e005702fa98b46ff588ca47e561cc581b65969d13e1f6496d07abe940b4a65b6850400b4a001702fdc036a62d1bf6196dc34fd280654d27eea0801128166e32edc65a53168df0f0d03333538ebd8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 12 Jul 2012 10:02:19 GMT" + }, + { + "cache-control": "max-age=53347291" + }, + { + "expires": "Mon, 14 Jul 2014 00:19:05 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:34 GMT" + }, + { + "content-length": "358" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 130, + "wire": "88cac2c9fae90f0d836dd7daea588ca47e561cc5804eb4179e6c1f6496d07abe940b8a6e2d6a0801654106e36f5c134a62d1bfc0ed", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Tue, 12 Jul 2011 15:59:09 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "5794" + }, + { + "content-type": "text/css" + }, + { + "cache-control": "max-age=27418850" + }, + { + "expires": "Mon, 16 Sep 2013 21:58:24 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:34 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 131, + "wire": "88ccc4f7eb6c96df3dbf4a044a65b6850400894082e005704053168dff588ca47e561cc581b6597da65b736496d07abe940b4a65b6850400b4a059b8266e32153168df6196dc34fd280654d27eea0801128166e32edc65b53168df0f0d8365d6dff1de", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 12 Jul 2012 10:02:20 GMT" + }, + { + "cache-control": "max-age=53394356" + }, + { + "expires": "Mon, 14 Jul 2014 13:23:31 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:35 GMT" + }, + { + "content-length": "3759" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 132, + "wire": "88e46c96df3dbf4a09c521aec504008940b7704cdc0bea62d1bf0f1390fe5a6d98d66a32bee3e16a41ba407f3f52848fd24a8fe46496df697e940baa6e2d6a0801654086e362b80754c5a37fcbf20f0d8365a0335f901d75d0620d263d4c741f71a0961ab4ffc2f5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Thu, 26 Apr 2012 15:23:19 GMT" + }, + { + "etag": "\"453b-4be96914da7c0\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Tue, 17 Sep 2013 11:52:07 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "3403" + }, + { + "content-type": "application/javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:37:35 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 133, + "wire": "88d4cc5f911d75d0620d263d4c795ba0fb8d04b0d5a7f46c96df3dbf4a044a65b6850400894082e005704d298b46ff588ca47e561cc581b65a79d75c6f6496df697e940b6a65b6850400b4a05bb8205c1014c5a37fc60f0d83719785f9e6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 12 Jul 2012 10:02:24 GMT" + }, + { + "cache-control": "max-age=53487765" + }, + { + "expires": "Tue, 15 Jul 2014 15:20:20 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:35 GMT" + }, + { + "content-length": "6382" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 134, + "wire": "88d8f3d0f70f0d830b206bc1588ca47e561cc581c13a1085917f6496df3dbf4a320535112a080169403f704cdc03ea62d1bf6196dc34fd280654d27eea0801128166e32edc65d53168df408721eaa8a4498f5788ea52d6b0e83772ffea", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Tue, 30 Oct 2012 09:22:55 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1304" + }, + { + "content-type": "application/x-javascript" + }, + { + "cache-control": "max-age=62711132" + }, + { + "expires": "Thu, 30 Oct 2014 09:23:09 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:37 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 135, + "wire": "88bfdc0f139afe597c523451c636a492324aec71b407996c5195a71b205ffe7f5891a47e561cc5802e89e0001f4a5761bb8d254089f2b511ad51c8324e5f834d96975f8b497ca58e83ee3412c3569f0f0d033238364088ea52d6b0e83772ff8749a929ed4c0d377f0388cc52d6b4341bb97f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:37:37 GMT" + }, + { + "server": "Apache" + }, + { + "etag": "\"392d4eaba4ddbcf7bb408352be465c19\"" + }, + { + "cache-control": "max-age=1728000, private" + }, + { + "x-lb-nocache": "true" + }, + { + "content-type": "text/javascript" + }, + { + "content-length": "286" + }, + { + "keep-alive": "timeout=45" + }, + { + "connection": "Keep-Alive" + } + ] + }, + { + "seqno": 136, + "wire": "88e1d9ca5a839bd9ab6c96df697e94640a6a225410022500fdc10ae36d298b46ff588ca47e561cc581c13a1081f7ff6496df3dbf4a320535112a080169403f7042b8cb8a62d1bfc80f0d836dd79ec7f3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 09:22:54 GMT" + }, + { + "cache-control": "max-age=62711099" + }, + { + "expires": "Thu, 30 Oct 2014 09:22:36 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:37 GMT" + }, + { + "content-length": "5788" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 137, + "wire": "887689bf7b3e65a193777b3fcf0f0d83134107c26196dc34fd280654d27eea0801128166e32edc65e53168df", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "2410" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:38 GMT" + } + ] + }, + { + "seqno": 138, + "wire": "88bee76495dc34fd2800a994752820000a0017000b800298b46f4085aec1cd48ff86a8eb10649cbf5886a8eb10649cbf4003703370caacf4189eac2cb07f33a535dc618f1e3c2f5164424695c87a58f0c918ad9ad7f34d1fcfd297b5c1fce9d5914bfbb5a97b56d534e4bea6bdd0a90dfd0a6ae1b54c9a6fa9a61e2a5ed5a3f90f0d0234337f09842507417f5f87352398ac4c697f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:37:38 GMT" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 01 Jan 2000 00:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.nedstat.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND NAV COM\"" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 139, + "wire": "88e5c95f88352398ac74acb37f6c96df3dbf4a05f521aec504008940b97196ae05f53168df6196c361be940094d27eea0801128266e01cb8db6a62d1bf6496dc34fd280654d27eea0801128266e01cb8db6a62d1bf4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb768344b2970f0d837de65c408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f55846c42699f5890aed8e8313e94a47e561cc581e71a003f", + "headers": [ + { + ":status": "200" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Thu, 19 Apr 2012 16:34:19 GMT" + }, + { + "date": "Fri, 02 Nov 2012 23:06:55 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 23:06:55 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "9836" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "52243" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 140, + "wire": "88d9f60f139afe597c523451c636a492324aec71b407996c5195a71b205ffe7f588eaec3771a4bf4a523f2b0e62c0c83d7d60f0d023437d5d4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:37:37 GMT" + }, + { + "server": "Apache" + }, + { + "etag": "\"392d4eaba4ddbcf7bb408352be465c19\"" + }, + { + "cache-control": "private, max-age=30" + }, + { + "x-lb-nocache": "true" + }, + { + "content-type": "text/javascript" + }, + { + "content-length": "47" + }, + { + "keep-alive": "timeout=45" + }, + { + "connection": "Keep-Alive" + } + ] + }, + { + "seqno": 141, + "wire": "88cfe00f0d03333237d3ce", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "327" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:38 GMT" + } + ] + }, + { + "seqno": 142, + "wire": "88f76c96d07abe9403ea65b68504008940b7700fdc1094c5a37ff0d40f0d03393730c9588ca47e561cc581b13ee3ce3edf6496e4593e9403ea65b6850400b4a05bb807ee05953168dfd1dc4084f2b563938d1f739a4523b0fe105b148ed9bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Mon, 09 Jul 2012 15:09:22 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "970" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "max-age=52968695" + }, + { + "expires": "Wed, 09 Jul 2014 15:09:13 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:38 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 143, + "wire": "88fbf35f87352398ac5754dfbfd86c96d07abe9403ea65b68504008940b7700fdc1054c5a37f0f0d82089c588ca47e561cc581b13ee3ce85cf6496e4593e9403ea65b6850400b4a05bb807ee32d298b46fd6e1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/png" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Mon, 09 Jul 2012 15:09:21 GMT" + }, + { + "content-length": "126" + }, + { + "cache-control": "max-age=52968716" + }, + { + "expires": "Wed, 09 Jul 2014 15:09:34 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:38 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 144, + "wire": "88768586b19272fff8d1dc6c96df697e94640a6a225410022500f5c13b702e298b46ff588ca47e561cc581c13a075d79af6496df3dbf4a320535112a080169403d704edc1094c5a37fda0f0d8365b7dbe5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/gif" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:27:16 GMT" + }, + { + "cache-control": "max-age=62707784" + }, + { + "expires": "Thu, 30 Oct 2014 08:27:22 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:38 GMT" + }, + { + "content-length": "3595" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 145, + "wire": "88c1fbc5df6c96df697e94640a6a225410022500f5c13971a7d4c5a37f0f0d830bcdbf588ca47e561cc581c13a075d6ddf6496df3dbf4a320535112a080169403d704e5c6db53168dfdde8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/png" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:26:49 GMT" + }, + { + "content-length": "1859" + }, + { + "cache-control": "max-age=62707757" + }, + { + "expires": "Thu, 30 Oct 2014 08:26:55 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:38 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 146, + "wire": "48826401c55f95497ca589d34d1f6a1271d882a60320eb3cf36fac1f0f1fcf9d29aee30c169ad78e3219721d7b7ab05a6b62c2d051a0a863c1eca24f0690ac585ee6418f521875a7dc03313ad3e271f89d69f69a6a27182d319645fa23fca4b218682a60e87b6ca8741914ad593f0f0d033331375888a47e561cc5802e036496dc34fd280654d27eea0801128166e340b81794c5a37fe1ec7b8b84842d695b05443c86aa6f6c96e4593e940854cb6d0a0801128266e059b8c854c5a37ff8e8", + "headers": [ + { + ":status": "301" + }, + { + "server": "Apache" + }, + { + "content-type": "text/html; charset=iso-8859-1" + }, + { + "location": "http://emp.bbci.co.uk/emp/releases/worldwide/revisions/749603_749269_749444_6/embed.js?mediaset=journalism-pc" + }, + { + "content-length": "317" + }, + { + "cache-control": "max-age=160" + }, + { + "expires": "Sat, 03 Nov 2012 13:40:18 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:38 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Wed, 11 Jul 2012 23:13:31 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 147, + "wire": "88cae7f5cfbfe80f0d830802d7588ca47e561cc581c13a1081e17f6496df3dbf4a320535112a080169403f7042b820298b46ffe5f0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Tue, 30 Oct 2012 09:22:54 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1014" + }, + { + "cache-control": "max-age=62711082" + }, + { + "expires": "Thu, 30 Oct 2014 09:22:20 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:38 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 148, + "wire": "88ccf70f1fcf9d29aee30c169ad78e3219721d7b7ab05a6b62c2d051a0a863c1eca24f0690ac585ee6418f521875a7dc03313ad3e271f89d69f69a6a27182d319645fa23fca4b218682a60e87b6ca8741914ad593f0f0d83700f835888a47e561cc5802e3b6496dc34fd280654d27eea0801128166e340b82714c5a37f6196dc34fd280654d27eea0801128166e32edc65f53168dff3c46c96df697e940bca6e2d6a0801128115c6dcb826d4c5a37ffeee", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "content-type": "application/x-javascript" + }, + { + "location": "http://emp.bbci.co.uk/emp/releases/worldwide/revisions/749603_749269_749444_6/embed.js?mediaset=journalism-pc" + }, + { + "content-length": "6090" + }, + { + "cache-control": "max-age=167" + }, + { + "expires": "Sat, 03 Nov 2012 13:40:26 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:39 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Tue, 18 Sep 2012 12:56:25 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 149, + "wire": "88769086b19272b025c4b85f53fae151bcff7f6c96df3dbf4a042a6a2254100225020b807ae09a53168dff0f138ffe47246b3448c8d900e3f238007f3f52848fd24a8f588ca47e561cc58190b6cb80003f6496df3dbf4a09a535112a080165403d7041b8d094c5a37fcaf30f0d8313aebfd9eef9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Thu, 11 Oct 2012 10:08:24 GMT" + }, + { + "etag": "\"adb-4cbc5c069d600\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Thu, 24 Oct 2013 08:21:42 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "2779" + }, + { + "content-type": "image/png" + }, + { + "date": "Sat, 03 Nov 2012 13:37:38 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 150, + "wire": "887f2bfdacf4189eac2cb07f33a535dc61898e79a828e442f32f21ed8e82928313aaf5152c56398a39189895455b35c4bf9a68fe7e94bdae0fe6f70da3521bfa06a5fc1c46a6f8721d4d7ba13a9af75f3a9ab86d53269bea70d3914d7c36a9934ef52fe0d0a6edf0a9af6e052f6ad0a69878a9ab7de534eac8a5fddad4bdab6ff35f96497ca58e83ee3412c3569fb50938ec4153070df8567be559871a52324f496a4ff66196dc34fd280654d27eea0801128166e32edc680a62d1bf768320e52f5885aec3771a4b0f0d830b6d37e7", + "headers": [ + { + ":status": "200" + }, + { + "p3p": "policyref=\"http://googleads.g.doubleclick.net/pagead/gcn_p3p_.xml\", CP=\"CURa ADMa DEVa TAIo PSAo PSDo OUR IND UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "text/javascript; charset=UTF-8" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-disposition": "attachment" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:40 GMT" + }, + { + "server": "cafe" + }, + { + "cache-control": "private" + }, + { + "content-length": "1545" + }, + { + "x-xss-protection": "1; mode=block" + } + ] + }, + { + "seqno": 151, + "wire": "88f55f911d75d0620d263d4c795ba0fb8d04b0d5a70f0d83136073fac1", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "2506" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:40 GMT" + } + ] + }, + { + "seqno": 152, + "wire": "88c96c96df697e940b6a612c6a08010a8172e32fdc13aa62d1bf0f1390fe5c682d2cd3e46da2148d352101fcffc8c76496df697e940baa6e2d6a0801654086e34fdc0b2a62d1bfd3fc0f0d8375c0bf5f901d75d0620d263d4c741f71a0961ab4ffce7f3488ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Tue, 15 Feb 2011 16:39:27 GMT" + }, + { + "etag": "\"6414-49c54cec44dc0\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Tue, 17 Sep 2013 11:49:13 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "7619" + }, + { + "content-type": "application/javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:37:39 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 153, + "wire": "88d55a839bd9abf36c96c361be940894d444a820044a08171a15c6dc53168dff6196c361be940094d27eea080112816ee01bb8db4a62d1bf6496dc34fd280654d27eea080112816ee01bb8db4a62d1bff2f10f0d8465d642eff055847821039fef", + "headers": [ + { + ":status": "200" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Fri, 12 Oct 2012 20:42:56 GMT" + }, + { + "date": "Fri, 02 Nov 2012 15:05:54 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 15:05:54 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "37317" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "81106" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 154, + "wire": "887689bf7b3e65a193777b3fc80f0d03333237c3cb", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "327" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:40 GMT" + } + ] + }, + { + "seqno": 155, + "wire": "88e6dbeac3ee588ca47e561cc581b13ee3ce881f6496e4593e9403ea65b6850400b4a05bb807ee32f298b46f6196dc34fd280654d27eea0801128166e32edc65e53168df0f0d8465c79907c7ee", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/png" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Mon, 09 Jul 2012 15:09:22 GMT" + }, + { + "cache-control": "max-age=52968720" + }, + { + "expires": "Wed, 09 Jul 2014 15:09:38 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:38 GMT" + }, + { + "content-length": "36830" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 156, + "wire": "88d1ce4085aec1cd48ff86a8eb10649cbf6496c361be940054ca3a940bef814002e001700053168dff5892a8eb10649cbf4a536a12b585ee3a0d20d25fcefac40f0d03333539f8c9", + "headers": [ + { + ":status": "200" + }, + { + "p3p": "policyref=\"http://googleads.g.doubleclick.net/pagead/gcn_p3p_.xml\", CP=\"CURa ADMa DEVa TAIo PSAo PSDo OUR IND UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "date": "Sat, 03 Nov 2012 13:37:40 GMT" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "application/x-javascript" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-length": "359" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 157, + "wire": "88c4ce0f0d03353330c9d1", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "530" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:40 GMT" + } + ] + }, + { + "seqno": 158, + "wire": "88ec6c96df697e94640a6a225410022500f5c13971a714c5a37fe2ca0f0d8369d13ff1588ca47e561cc581c13a075d6daf6496df3dbf4a320535112a080169403d704e5c6da53168dfd4cdf4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:26:46 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "4729" + }, + { + "content-type": "image/png" + }, + { + "cache-control": "max-age=62707754" + }, + { + "expires": "Thu, 30 Oct 2014 08:26:54 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:40 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 159, + "wire": "88efe4f3f4cc6c96df697e94640a6a225410022500f5c13b719654c5a37f0f0d820b20588ca47e561cc581c13a075e03bf6496df3dbf4a320535112a080169403d704edc69d53168dfd7d0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/png" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:27:33 GMT" + }, + { + "content-length": "130" + }, + { + "cache-control": "max-age=62707807" + }, + { + "expires": "Thu, 30 Oct 2014 08:27:47 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:40 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 160, + "wire": "88f26497d07abe94032a693f750400b4a059b8cbb719794c5a37ff54012a5f88352398ac74acb37f0f0d84782fbe0fcad3408af2b10649cab0c8931eaf044d4953534088f2b10649cab0e62f0130589aaec3771a4bf4a523f2b0e62c00fa529b5095ac2f71d0690692ff4089f2b511ad51c8324e5f834d96977b05582d43444e6c96e4593e94138a6e2d6a080112807ee32ddc682a62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Nov 2014 13:37:38 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "81990" + }, + { + "date": "Sat, 03 Nov 2012 13:37:38 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-action": "MISS" + }, + { + "x-cache-age": "0" + }, + { + "cache-control": "private, max-age=0, must-revalidate" + }, + { + "x-lb-nocache": "true" + }, + { + "vary": "X-CDN" + }, + { + "last-modified": "Wed, 26 Sep 2012 09:35:41 GMT" + } + ] + }, + { + "seqno": 161, + "wire": "488264027688aa6355e580ae25c16196dc34fd280654d27eea0801128166e32edc69953168df0f0d01307f1d842507417f0f1fffd8019d29aee30c0e458b4946bc87b63a0a4a0c4eabd454b0393a31a44dbc15c22138db8b884169d0099704e082c5d71a109a7c2bbdf68f700440f2c83ec94189d4104e94d771860722f21ed8e82928313aaf5152c128313aaacdd9d566ff77986641098658030a886c76559ba271a71d7c026d566e81602acdd0aacdd69f038e36e36ab375a7560880c320559bad3ad0990800c34eb4cbce36cb01559baab37557701faf7559beab375141d2ab37eb1d89a8b6451da949ea0aacdd47b559be1103cb20559ba8291352acdfa8be10ab37489f5595566f90f524b525566ed45f08559be3a4b61883559bb61652d9616c559beab37643d23354ab37fc78f0bc7191721d7b7aaacddb0b296cb0b64521e919aa559bfe3c785e38c8b90ebdbd5566ed8832acdf559bb39472506a8aab37ec3d3517d5761e9320a8b50a89b13b614741271d532acdd55dc084110ab37d5665fb3d9240f32dbce07fcf", + "headers": [ + { + ":status": "302" + }, + { + "server": "nginx/1.2.0" + }, + { + "date": "Sat, 03 Nov 2012 13:37:43 GMT" + }, + { + "content-length": "0" + }, + { + "connection": "close" + }, + { + "location": "http://ad-emea.doubleclick.net/adj/N2581.122656.2214702362621/B6422491.8;sz=120x30;click0=http://ad.doubleclick.net/click%3Bh%3Dv8/3d22/3/0/%2a/q%3B264679025%3B0-0%3B1%3B49066565%3B47-120/30%3B47423100/47438653/1%3B%3B%7Eokv%3D%3Bslot%3Dpartner_button1%3Bsz%3D120x30%3Bsectn%3Dnews%3Bctype%3Dcontent%3Bnews%3Damerica%3Breferrer%3D%3Bdomain%3Dwww.bbc.co.uk%3Breferrer_domain%3Dwww.bbc.co.uk%3Brsi%3D%3Bheadline%3Dromneypromisesus%2527realchang%3B%7Esscs%3D%3f;ord=835861?" + } + ] + }, + { + "seqno": 162, + "wire": "88c0bf5f87352398ac4c697f0f0d023433bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx/1.2.0" + }, + { + "date": "Sat, 03 Nov 2012 13:37:43 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "connection": "close" + } + ] + }, + { + "seqno": 163, + "wire": "88d8e20f0d03343833dd6196dc34fd280654d27eea0801128166e32edc69a53168df", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "483" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:44 GMT" + } + ] + }, + { + "seqno": 164, + "wire": "88f6deca6c96df697e940b6a681fa5040089410ae01ab8dbea62d1bf6196c361be940094d27eea0801128172e00171b1298b46ff6496dc34fd280654d27eea0801128172e00171b1298b46ff4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb768344b2970f0d83682d39408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f558475d7822f5890aed8e8313e94a47e561cc581e71a003f", + "headers": [ + { + ":status": "200" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Tue, 15 May 2012 22:04:59 GMT" + }, + { + "date": "Fri, 02 Nov 2012 16:00:52 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 16:00:52 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "4146" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "77812" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 165, + "wire": "88d26c96c361be940854d03b14100215042b816ee32d298b46ff6196c361be940094d27eea080112817ee361b8cb8a62d1bf6496dc34fd280654d27eea080112817ee361b8cb8a62d1bfc5c40f0d8365e65ac355847197dc7bc27b8b84842d695b05443c86aa6feb", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Fri, 11 Mar 2011 22:15:34 GMT" + }, + { + "date": "Fri, 02 Nov 2012 19:51:36 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 19:51:36 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "3834" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "63968" + }, + { + "cache-control": "public, max-age=86400" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 166, + "wire": "88e6f00f0d03333236ebcb", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "326" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:44 GMT" + } + ] + }, + { + "seqno": 167, + "wire": "88e6f00f0d821045ebcb", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "212" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:44 GMT" + } + ] + }, + { + "seqno": 168, + "wire": "880f28e1b1288a1861860d19edbaf39b11f9d711aff0f0e6787c3992bc3bb6d1a7878bbbdaa30d78b6209c3f45838831eb7bed4be7a466aa05ec2f7410cbd454fda983cd66b0a88375b57d280656d27eeb08016540b371976e34d298b46ffb5358d33c0c7f4088f2b5761c8b48348f89ae46568e61a00e2cff7f38e9acf4189eac2cb07f33a535dc618e885ec2f7410cbd454b1e19231620d5b35afe69a3f9fa52f6b83f9d3ab4a9af742a6bdd7d4c9c6153271bea6adfad4dd0e853269bea70d3914d7c36a97b568534c3c54c9a77a97f06852f69dea6edf0a9af6e05356fbca63c10ff3f76035253495886a8eb10649cbfe66496df3dbf4a002a651d4a05f740a0017000b800298b46ff5f9a1d75d0620d263d4c741f71a0961ab4fd9271d882a60e1bf0acf7798624f6d5d4b27ff2c5d2", + "headers": [ + { + ":status": "200" + }, + { + "set-cookie": "rts_AAAA=MLuB86QsXkGiDUw6LAw6IpFSRlNUwBT4lFpGQscUZ2EV0HP8; Domain=.revsci.net; Expires=Sun, 03-Nov-2013 13:37:44 GMT; Path=/" + }, + { + "x-proc-data": "pd3-bgas06-9" + }, + { + "p3p": "policyref=\"http://js.revsci.net/w3c/rsip3p.xml\", CP=\"NON PSA PSD IVA IVD OTP SAM IND UNI PUR COM NAV INT DEM CNT STA PRE OTC HEA\"" + }, + { + "server": "RSI" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "content-type": "application/javascript;charset=UTF-8" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "date": "Sat, 03 Nov 2012 13:37:44 GMT" + } + ] + }, + { + "seqno": 169, + "wire": "88d25f8b497ca58e83ee3412c3569f0f0d03313733f40f28eaef00642b8db4f15a182464ad363748fbe18de8e51c8d81f6c250b4dbd232fb8b38e32d01e65f6c0fb61289e7996a265971cfb50be6b3585441b869fa500cada4fdd610022502d5c03b71a694c5a37fda958d33c0c7da921e919aa8172cb294893772d251a2db0abd454fc2640130768821e8a0a4498f5041408b20c9395690d614893772ff86a8eb10649cbf408caec1cd48d690d614893772ff86a8eb10649cbfee7f09a7acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725ffe7f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:37:44 GMT" + }, + { + "content-type": "text/javascript" + }, + { + "content-length": "173" + }, + { + "connection": "keep-alive" + }, + { + "set-cookie": "v=1de6548e4a0d3e45a7c991b8bfad50951e1458d396-6634083950951e28834_3366; expires=Sat, 03-Nov-2012 14:07:44 GMT; path=/; domain=.effectivemeasure.net" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "server": "collection10" + }, + { + "cache-directive": "no-cache" + }, + { + "pragma-directive": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID\"" + } + ] + }, + { + "seqno": 170, + "wire": "dd6196dc34fd280654d27eea0801128166e32edc6c0a62d1bf768dd06258741e54ad9326e61c5c1f7f01c3d6ceb51652b3d0627ab0b2c1fcce94d771863c78f0b8e496d418f52e43d2c78648c0e496d418f52fe69a3f9fa52f6b83f9d3ab4a97f76b52f6adaa5ee1b46a6fc90ff34089f2b567f05b0b22d1fa868776b5f4e0df408cf2b0d15d454addcb620c7abf8712e05db03a277ff40f1faf9d29aee30c78f1e171c92da831ea5c87a58864dc5b3b96c6242ca3b684ae3457e7fc2c06f8a0d2401038d07e08983fcc64022d315f92497ca589d34d1f6a1271d882a60b532acf7f0f0d03313838", + "headers": [ + { + ":status": "302" + }, + { + "date": "Sat, 03 Nov 2012 13:37:50 GMT" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "P3P - policyref=\"http://www.adfusion.com/w3c/adfusion.xml\", CP=\"NON DSP COR CURa TIA\"" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "pragma": "no-cache" + }, + { + "location": "http://www.adfusion.com/AdServer/default.aspx?e=i&lid=10641&ct=" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "-1" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-length": "188" + } + ] + }, + { + "seqno": 171, + "wire": "88768586b19272ff52848fd24a8fed5585134db6f07f6196dc34fd280654d27eea0801128166e32edc69b53168df6c96d07abe940b4a693f7504008540bd71915c0b8a62d1bf0f0d8369a6857f2788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "accept-ranges": "bytes" + }, + { + "content-type": "image/jpeg" + }, + { + "age": "245581" + }, + { + "date": "Sat, 03 Nov 2012 13:37:45 GMT" + }, + { + "last-modified": "Mon, 14 Nov 2011 18:32:16 GMT" + }, + { + "content-length": "4442" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 172, + "wire": "88cac9c8c7c6fc0f1faf9d29aee30c78f1e171c92da831ea5c87a58864dc5b3b96c6242ca3b684ae3457e7fc2c06f8a0d2401038d07e08983fd4c5c40f0d8369a69f0f28cf21a481c9401034f36b4ad81d59a1b45586d3cdad296375c0bf24600b3f6a487a466aa05c724b6a0c7a9721e9fb50be6b3585441c8b27d2800ad94752c20080a01cb8005c0014c5a37fda958d33c0c7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:37:50 GMT" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "P3P - policyref=\"http://www.adfusion.com/w3c/adfusion.xml\", CP=\"NON DSP COR CURa TIA\"" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "pragma": "no-cache" + }, + { + "location": "http://www.adfusion.com/AdServer/default.aspx?e=i&lid=10641&ct=" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "-1" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-length": "4449" + }, + { + "set-cookie": "cid=6f010485-f507-4a4e-a485-feb7619db013; domain=.adfusion.com; expires=Wed, 01-Jan-2020 06:00:00 GMT; path=/" + } + ] + }, + { + "seqno": 173, + "wire": "88c3c2e655847596c2ffc16c96c361be94101486bb1410022502ddc6c171b754c5a37f0f0d830ba07fc0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "accept-ranges": "bytes" + }, + { + "content-type": "image/gif" + }, + { + "age": "73519" + }, + { + "date": "Sat, 03 Nov 2012 13:37:45 GMT" + }, + { + "last-modified": "Fri, 20 Apr 2012 15:50:57 GMT" + }, + { + "content-length": "1709" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 174, + "wire": "88c5c4f355850ba275e6ffc36c96e4593e94109486d99410022502edc6deb8d3ca62d1bf0f0d83680cb9c2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "accept-ranges": "bytes" + }, + { + "content-type": "image/jpeg" + }, + { + "age": "172785" + }, + { + "date": "Sat, 03 Nov 2012 13:37:45 GMT" + }, + { + "last-modified": "Wed, 22 Aug 2012 17:58:48 GMT" + }, + { + "content-length": "4036" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 175, + "wire": "88c7c6f555840880fbc0c56c96c361be940094be522820042a08371b0dc6df53168dff0f0d8369f0bfc4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "accept-ranges": "bytes" + }, + { + "content-type": "image/jpeg" + }, + { + "age": "120980" + }, + { + "date": "Sat, 03 Nov 2012 13:37:45 GMT" + }, + { + "last-modified": "Fri, 02 Dec 2011 21:51:59 GMT" + }, + { + "content-length": "4919" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 176, + "wire": "88c9deec5a839bd9ab6c96d07abe9403ea65b68504008940b7700fdc1094c5a37f588ca47e561cc581b13ee3ce85af6496e4593e9403ea65b6850400b4a05bb807ee32f298b46fef0f0d830844f7c84084f2b563938d1f739a4523b0fe105b148ed9bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/gif" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Mon, 09 Jul 2012 15:09:22 GMT" + }, + { + "cache-control": "max-age=52968714" + }, + { + "expires": "Wed, 09 Jul 2014 15:09:38 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:44 GMT" + }, + { + "content-length": "1128" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 177, + "wire": "895f911d75d0620d263d4c795ba0fb8d04b0d5a7e4c36496d07abe940054ca3a940bef814002e001700053168dfff20f0d0130cb58b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bf4085aec1cd48ff86a8eb10649cbf", + "headers": [ + { + ":status": "204" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:44 GMT" + }, + { + "content-length": "0" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 178, + "wire": "88d2e76c96df697e940894cb6d0a08010a816ee36fdc0894c5a37fd2c70f0d82101d5f87352398ac5754df588ca47e561cc5804eb4179f13ff6496d07abe940b8a6e2d6a0801654106e36fdc6d953168dff8d1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Tue, 12 Jul 2011 15:59:12 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "207" + }, + { + "content-type": "image/png" + }, + { + "cache-control": "max-age=27418929" + }, + { + "expires": "Mon, 16 Sep 2013 21:59:53 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:44 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 179, + "wire": "88d66c96c361be94138a6a225410022502fdc6ddb82694c5a37feccb0f0d841380107f5f88352398ac74acb37f588ca47e561cc581c134065c13ff6496dd6d5f4a09c535112a08016940bf71b7ae05a53168dfd7d5ca", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Fri, 26 Oct 2012 19:57:24 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "26021" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=62403629" + }, + { + "expires": "Sun, 26 Oct 2014 19:58:14 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:45 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 180, + "wire": "88769086b19272b025c4b85f53fae151bcff7ff05898a47e561cc5819003e94aed8e8313e9442d48fc8e62c011035f901d75d0620d263d4c741f71a0961ab4ffd16196dc34fd280654d27eea0801128166e32edc13ca62d1bf4088ea52d6b0e83772ff8d49a929ed4c0dfd2948fcc0f3626496dc34fd280654d27eea0801128166e342b82794c5a37fdf0f1390fe5e6db02cd11d91e91d7e379c203f9f6c96e4593e94109486d994100225021b816ae042a62d1bff0f0d840882f07f7f1d88cc52d6b4341bb97f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "vary": "Accept-Encoding" + }, + { + "cache-control": "max-age=300, public, s-maxage=120" + }, + { + "content-type": "application/javascript" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:28 GMT" + }, + { + "keep-alive": "timeout=5, max=852" + }, + { + "expires": "Sat, 03 Nov 2012 13:42:28 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"8550-4c7d8d79b86c0\"" + }, + { + "last-modified": "Wed, 22 Aug 2012 11:14:11 GMT" + }, + { + "content-length": "12181" + }, + { + "connection": "Keep-Alive" + } + ] + }, + { + "seqno": 181, + "wire": "88c56c96e4593e94109486d994100225021b816ae05953168dff0f1390fe5908df59a23b23d23b18c11b40fe7fe2c56496dc34fd280654d27eea0801128166e32f5c65953168dff9d80f0d83132e355f86497ca582211f6196dc34fd280654d27eea0801128166e32edc69c53168dfe1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Wed, 22 Aug 2012 11:14:13 GMT" + }, + { + "etag": "\"31a9-4c7d8d7ba0b40\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=300, public, s-maxage=120" + }, + { + "expires": "Sat, 03 Nov 2012 13:38:33 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "2364" + }, + { + "content-type": "text/css" + }, + { + "date": "Sat, 03 Nov 2012 13:37:46 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 182, + "wire": "88e65f961d75d0620d263d4c7441eafb24e3b1054c1c37e159ef54012a409419085421621ea4d87a161d141fc2d495339e447f95d7ab76ffa53160dff4a6be1bfe94d5af7e4d5a777f409419085421621ea4d87a161d141fc2d3947216c47f99bc7a925a92b6ff5597e94fc5b697b5a5424b22dc8c99fe94f90f0d82085d5887a47e561cc5801f6196dc34fd280654d27eea0801128166e32edc69d53168dfe7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "content-type": "application/json;charset=UTF-8" + }, + { + "access-control-allow-origin": "*" + }, + { + "access-control-allow-methods": "POST, GET, PUT, OPTIONS" + }, + { + "access-control-allow-headers": "Content-Type, X-Requested-With, *" + }, + { + "content-length": "117" + }, + { + "cache-control": "max-age=0" + }, + { + "date": "Sat, 03 Nov 2012 13:37:47 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 183, + "wire": "88cf6c96e4593e94109486d994100225021b816ae01e53168dff0f138ffe4411156688ec8f48eb92100007f3ec588ca47e561cc58190b6cb80003f6496df697e940baa6e2d6a0801654086e360b8cbea62d1bf7b8b84842d695b05443c86aa6fe40f0d8379a7dadac2eb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "last-modified": "Wed, 22 Aug 2012 11:14:08 GMT" + }, + { + "etag": "\"212e-4c7d8d76dc000\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=31536000" + }, + { + "expires": "Tue, 17 Sep 2013 11:50:39 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "8494" + }, + { + "content-type": "image/png" + }, + { + "date": "Sat, 03 Nov 2012 13:37:47 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 184, + "wire": "886196dc34fd280654d27eea0801128166e32edc6dd53168dff16495dc34fd2800a994752820000a0017000b800298b46fde5886a8eb10649cbf7f39caacf4189eac2cb07f33a535dc618f1e3c2f5164424695c87a58f0c918ad9ad7f34d1fcfd297b5c1fce9d5914bfbb5a97b56d534e4bea6bdd0a90dfd0a6ae1b54c9a6fa9a61e2a5ed5a3f90f0d0234337f11842507417f5f87352398ac4c697f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:37:57 GMT" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 01 Jan 2000 00:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.nedstat.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND NAV COM\"" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 185, + "wire": "88f6c4e0ea6c96d07abe9403ea65b68504008940b7700fdc132a62d1bf588ca47e561cc581b13ee3ce3adf6496e4593e9403ea65b6850400b4a05bb807ee044a62d1bfc60f0d82089cf4e9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/png" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Mon, 09 Jul 2012 15:09:23 GMT" + }, + { + "cache-control": "max-age=52968675" + }, + { + "expires": "Wed, 09 Jul 2014 15:09:12 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:57 GMT" + }, + { + "content-length": "126" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 186, + "wire": "887689bf7b3e65a193777b3fe90f0d03353735ee6196dc34fd280654d27eea0801128166e32edc6de53168df", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "575" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:58 GMT" + } + ] + }, + { + "seqno": 187, + "wire": "48826402768c86b19272ad78fe8e92b015c30f1ffff3019d29aee30c535ae3ae92c72ae43d2c0e4625a580b2000567997197d6109d71b583fe535a6079b640ebff14d7dc904e94d771860722f21ed8e82928313aaf5152c128313aaacdd9d566ff77986641098658030a886c77559ba271a71a03adb2ab3740b01566e81566ebacb8dbed81c559bacb4db4b3a27987c0ab375a75b13416db61a75b65f71d6580aacdd559baabb80fd7baacdf559ba8a0e9559bf4147216c8ce3b24559ba8f6ab37dd13de5f02a2bcfba0f2e38a8af3ee83cbe054579f741e44d81566ea0a44d4ab37ea2f842acdd227d565559be43d492d49559bb517c21566ff83d9448ab376c2ca5b2c2d8ab37ea2f84783d94496a083a8720c40081a7c4faacdd90f48cd52acdff1e3c2f1c645c875edeaab376c2ca5b2c2d91487a466a9566ff8f1e178e322e43af6f5559bb620cab37d566ece51c941aa2aacdf8cf4c7d4d4508ac7d4c848ea3567a0c9310c3a9566eaaee042088559beab32fc4e742601d09947652bd2590c3ae82f95c87a7f0f0d0130c0", + "headers": [ + { + ":status": "302" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "location": "http://mp.apmebf.com/ad/fm/13001-83639-22765-1?mpt=853079&mpvc=http://ad.doubleclick.net/click%3Bh%3Dv8/3d22/3/0/%2a/v%3B264640753%3B0-0%3B0%3B73659506%3B3454-728/90%3B47524155/47539673/1%3B%3B%7Eokv%3D%3Bslot%3Dleaderboard%3Bsz%3D728x90%2C970x66%2C970x90%2C970x250%3Bsectn%3Dnews%3Bctype%3Dcontent%3Bnews%3Dworld%3Breferrer%3Dnewsworlduscanada20104929%3Bdomain%3Dwww.bbc.co.uk%3Breferrer_domain%3Dwww.bbc.co.uk%3Brsi%3D%3Bheadline%3Dbombkillspakistanipolitician%3B%7Esscs%3D%3f&host=altfarm.mediaplex.com" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:37:58 GMT" + } + ] + }, + { + "seqno": 188, + "wire": "88c0fd6c96e4593e940054d03b141000e2816ee059b8db8a62d1bf52848fd24a8f0f0d0234335896a47e561cc5801f4a547588324e5837152b5e39fa98bf6496dc34fd280654d27eea0801128166e32edc6de53168df7f218e49a929ed4c0107d2948fcc0175cfdeca", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:37:58 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Wed, 01 Mar 2006 15:13:56 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "43" + }, + { + "cache-control": "max-age=0, no-cache=Set-Cookie" + }, + { + "expires": "Sat, 03 Nov 2012 13:37:58 GMT" + }, + { + "keep-alive": "timeout=10, max=176" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 189, + "wire": "88c6f10f0d03333431f6c5", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "341" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:58 GMT" + } + ] + }, + { + "seqno": 190, + "wire": "887f0dfdacf4189eac2cb07f33a535dc61898e79a828e442f32f21ed8e82928313aaf5152c56398a39189895455b35c4bf9a68fe7e94bdae0fe6f70da3521bfa06a5fc1c46a6f8721d4d7ba13a9af75f3a9ab86d53269bea70d3914d7c36a9934ef52fe0d0a6edf0a9af6e052f6ad0a69878a9ab7de534eac8a5fddad4bdab6ff35f96497ca58e83ee3412c3569fb50938ec4153070df8567b4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb59871a52324f496a4f5a839bd9ab6196dc34fd280654d27eea0801128166e32edc6df53168df768320e52f5885aec3771a4b0f0d830b2f3b408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f", + "headers": [ + { + ":status": "200" + }, + { + "p3p": "policyref=\"http://googleads.g.doubleclick.net/pagead/gcn_p3p_.xml\", CP=\"CURa ADMa DEVa TAIo PSAo PSDo OUR IND UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "text/javascript; charset=UTF-8" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-disposition": "attachment" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:59 GMT" + }, + { + "server": "cafe" + }, + { + "cache-control": "private" + }, + { + "content-length": "1387" + }, + { + "x-xss-protection": "1; mode=block" + } + ] + }, + { + "seqno": 191, + "wire": "88768586b19272ff6c96dc34fd280654d27eea0801128072e360b8d3aa62d1bfdbc40f0d8369d037f3588ca47e561cc581c640d3ae081f6496d07abe94032a693f750400b4a01cb8d86e32f298b46fd27f1988ea52d6b0e83772ff4084f2b563938d1f739a4523b0fe105b148ed9bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Sat, 03 Nov 2012 06:50:47 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "4705" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=63047620" + }, + { + "expires": "Mon, 03 Nov 2014 06:51:38 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:58 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 192, + "wire": "d3c7c37f0dbcacf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9af7427535eebe753570daa64d37d4e1a72297b568534c3c7f9f0f28c7dd04c16bb9d6682cac165b0bed3ef3af85a6d6f43fb5243d2335502e3ae92c72ae43d3f6a5634cf031f6a17cd66b0a88341eafa500cada4fdd61002d28166e32edc6df53168dff0f1ffffa019d29aee30c0e84ca3b295e92c861d7417cae43d2c0e4625a580b2000567997197d6109d71b583fe535a6079b640ebff14d7dc904e94d771860722f21ed8e82928313aaf5152c128313aaacdd9d566ff77986641098658030a886c77559ba271a71a03adb2ab3740b01566e81566ebacb8dbed81c559bacb4db4b3a27987c0ab375a75b13416db61a75b65f71d6580aacdd559baabb80fd7baacdf559ba8a0e9559bf4147216c8ce3b24559ba8f6ab37dd13de5f02a2bcfba0f2e38a8af3ee83cbe054579f741e44d81566ea0a44d4ab37ea2f842acdd227d565559be43d492d49559bb517c21566ff83d9448ab376c2ca5b2c2d8ab37ea2f84783d94496a083a8720c40081a7c4faacdd90f48cd52acdff1e3c2f1c645c875edeaab376c2ca5b2c2d91487a466a9566ff8f1e178e322e43af6f5559bb620cab37d566ece51c941aa2aacdf8cf4c7d4d4508ac7d4c848ea3567a0c9310c3a9566eaaee042088559beab32fc547889d222401f8b6b41a481b69b69e6c0e89a6430f0d8274217f0f8749a929ed4c0dffef5f95497ca589d34d1f6a1271d882a60320eb3cf36fac1f", + "headers": [ + { + ":status": "302" + }, + { + "date": "Sat, 03 Nov 2012 13:37:59 GMT" + }, + { + "server": "Apache" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR PSAo PSDo OUR IND UNI COM NAV\"" + }, + { + "set-cookie": "S=g14vo-413-1351949879145-ya; domain=.apmebf.com; path=/; expires=Mon, 03-Nov-2014 13:37:59 GMT" + }, + { + "location": "http://altfarm.mediaplex.com/ad/fm/13001-83639-22765-1?mpt=853079&mpvc=http://ad.doubleclick.net/click%3Bh%3Dv8/3d22/3/0/%2a/v%3B264640753%3B0-0%3B0%3B73659506%3B3454-728/90%3B47524155/47539673/1%3B%3B%7Eokv%3D%3Bslot%3Dleaderboard%3Bsz%3D728x90%2C970x66%2C970x90%2C970x250%3Bsectn%3Dnews%3Bctype%3Dcontent%3Bnews%3Dworld%3Breferrer%3Dnewsworlduscanada20104929%3Bdomain%3Dwww.bbc.co.uk%3Breferrer_domain%3Dwww.bbc.co.uk%3Brsi%3D%3Bheadline%3Dbombkillspakistanipolitician%3B%7Esscs%3D%3f&no_cj_c=1&upsid=545485072431" + }, + { + "content-length": "711" + }, + { + "keep-alive": "timeout=5" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "text/html; charset=iso-8859-1" + } + ] + }, + { + "seqno": 193, + "wire": "d6d55886a8eb2127b0bf4085aec1cd48ff86a8eb10649cbf640130c30f28c0a4fd0ecc0164000dc109d71bfb50be6b3585441badabe9412da4fdd61002d28172e09eb810298b46ffb52b1a67818fb5243d2335502f496430eba0be5721e9fb0f1fffa8029d29aee30c1a9997a4b21875d05f2b90f4b043d492d49600c0590002c3a27bcbe0880f81b08a2d1c77c5b923aa41d922f3a69a3fca6b27580742651d94af496430eba0be5721e954584722a2c24eaa8b08590002b3ccb8cbeb084eb8dac1559c34d69559bef36c81d7fe29ad303cdb2075ff8a6bee48274a6bb8c30391790f6c741494189d57a8a96094189d5566eceab37fbbcc332084c32c018544363baacdd138d38d01d6d9559ba0580ab3740ab375d65c6df6c0e2acdd65a6da59d13cc3e0559bad3ad89a0b6db0d3adb2fb8eb2c05566eaacdd55dc07ebdd566faacdd45074aacdfa0a390b64671d922acdd47b559bee89ef2f81515e7dd07971c54579f741e5f02a2bcfba0f226c0ab37505226a559bf517c21566e913eab2aacdf21ea496a4aacdda8be10ab37fc1eca24559bb61652d9616c559bf517c23c1eca24b5041d4390620040d3e27d566ec87a466a9566ff8f1e178e322e43af6f5559bb61652d9616c8a43d23354ab37fc78f0bc7191721d7b7aaacddb106559beab376728e4a0d515566fc67a63ea6a284563ea6424751ab3d06498861d4ab3755770210442acdf55997f0f0d0130da", + "headers": [ + { + ":status": "302" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "cache-control": "no-store" + }, + { + "pragma": "no-cache" + }, + { + "expires": "0" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR PSAo PSDo OUR IND UNI COM NAV\"" + }, + { + "set-cookie": "mojo3=13001:22765; expires=Sun, 2-Nov-2014 16:28:10 GMT; path=/; domain=.mediaplex.com;" + }, + { + "location": "http://img.mediaplex.com/content/0/13001/728x90_090512_MVT_Standard.html?mpck=altfarm.mediaplex.com%2Fad%2Fck%2F13001-83639-22765-1%3Fmpt%3D853079&mpt=853079&mpvc=http://ad.doubleclick.net/click%3Bh%3Dv8/3d22/3/0/%2a/v%3B264640753%3B0-0%3B0%3B73659506%3B3454-728/90%3B47524155/47539673/1%3B%3B%7Eokv%3D%3Bslot%3Dleaderboard%3Bsz%3D728x90%2C970x66%2C970x90%2C970x250%3Bsectn%3Dnews%3Bctype%3Dcontent%3Bnews%3Dworld%3Breferrer%3Dnewsworlduscanada20104929%3Bdomain%3Dwww.bbc.co.uk%3Breferrer_domain%3Dwww.bbc.co.uk%3Brsi%3D%3Bheadline%3Dbombkillspakistanipolitician%3B%7Esscs%3D%3f" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:37:58 GMT" + } + ] + }, + { + "seqno": 194, + "wire": "88cdc96c96df697e9403ea6a225410022502cdc65ab8d814c5a37f0f1394fe46dca2009665b75668918c0e39295d13c0fe7fd70f0d83089e6fc3f45f96497ca589d34d1f6a1271d882a60c9bb52cf3cdbeb07f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:37:59 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Tue, 09 Oct 2012 13:34:50 GMT" + }, + { + "etag": "\"a5f202-357-4cba066fe7280\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "1285" + }, + { + "keep-alive": "timeout=5" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "text/html; charset=ISO-8859-1" + } + ] + }, + { + "seqno": 195, + "wire": "88dd5f911d75d0620d263d4c795ba0fb8d04b0d5a70f0d8313617bd1d0", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "2518" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:59 GMT" + } + ] + }, + { + "seqno": 196, + "wire": "88debe0f0d03333339d1d0", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "339" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:59 GMT" + } + ] + }, + { + "seqno": 197, + "wire": "88cce8e2d16c96dc34fd280654d27eea080112810dc139700fa98b46ff588ca47e561cc581c640e34271ff6496d07abe94032a693f750400b4a043704fdc03aa62d1bfe00f0d840bcd09ffcbca", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/gif" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Sat, 03 Nov 2012 11:26:09 GMT" + }, + { + "cache-control": "max-age=63064269" + }, + { + "expires": "Mon, 03 Nov 2014 11:29:07 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:58 GMT" + }, + { + "content-length": "18429" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 198, + "wire": "88e1c10f0d03333332d4d3", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "332" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:59 GMT" + } + ] + }, + { + "seqno": 199, + "wire": "88d8d3c56496c361be940054ca3a940bef814002e001700053168dff5892a8eb10649cbf4a536a12b585ee3a0d20d25fc3d8e30f0d03333332d2d6", + "headers": [ + { + ":status": "200" + }, + { + "p3p": "policyref=\"http://googleads.g.doubleclick.net/pagead/gcn_p3p_.xml\", CP=\"CURa ADMa DEVa TAIo PSAo PSDo OUR IND UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "date": "Sat, 03 Nov 2012 13:37:59 GMT" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "application/x-javascript" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-length": "332" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 200, + "wire": "88e3c30f0d03333339d6d5", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "339" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:59 GMT" + } + ] + }, + { + "seqno": 201, + "wire": "88d16496dc34fd280654d27eea0801128166e32edc6db53168dff65f87497ca589d34d1f0f0d8479a109cf6196dc34fd280654d27eea0801128166e32edc6db53168dfd0408af2b10649cab0c8931eaf044d4953534088f2b10649cab0e62f0130589aaec3771a4bf4a523f2b0e62c00fa529b5095ac2f71d0690692ff4089f2b511ad51c8324e5f834d96977b05582d43444e", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 03 Nov 2012 13:37:55 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "text/html" + }, + { + "content-length": "84226" + }, + { + "date": "Sat, 03 Nov 2012 13:37:55 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-action": "MISS" + }, + { + "x-cache-age": "0" + }, + { + "cache-control": "private, max-age=0, must-revalidate" + }, + { + "x-lb-nocache": "true" + }, + { + "vary": "X-CDN" + } + ] + }, + { + "seqno": 202, + "wire": "88e8cf58b1a47e561cc5801f4a547588324e5fa52a3ac849ec2fd295d86ee3497e94a6d4256b0bdc741a41a4bf4a216a47e47316007f6495dc34a9a4fdd4032a059b8cbb71b7d4e1bef2820045e07f16afbdae0fe74eac8a5ee1b46a437f40d4bf8388d4df0e41a9ab86d52ef0dca64d37d4e1a72297b568534c3c54c9a77ff35f91497ca589d34d1f649c7620a98386fc2b3d0f0d83089d67e1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "expires": "Sat Nov 03 13:37:59 UTC 2012" + }, + { + "content-encoding": "gzip" + }, + { + "p3p": "CP=\"NOI CURa ADMa DEVa TAIa OUR BUS IND UNI COM NAV INT\"" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-length": "1273" + }, + { + "date": "Sat, 03 Nov 2012 13:37:59 GMT" + } + ] + }, + { + "seqno": 203, + "wire": "88efcf0f0d821045e2e1", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "212" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:37:59 GMT" + } + ] + }, + { + "seqno": 204, + "wire": "ed6196dc34fd280654d27eea0801128166e32f5c036a62d1bf768dd06258741e54ad9326e61c5c1f7f02c3d6ceb51652b3d0627ab0b2c1fcce94d771863c78f0b8e496d418f52e43d2c78648c0e496d418f52fe69a3f9fa52f6b83f9d3ab4a97f76b52f6adaa5ee1b46a6fc90ff34089f2b567f05b0b22d1fa868776b5f4e0df408cf2b0d15d454addcb620c7abf8712e05db03a277fd80f1faf9d29aee30c78f1e171c92da831ea5c87a58864dc5b3b96c6242ca3b684ae3457e7fc2c06f8a0d2401038d07e08983ffb64022d315f92497ca589d34d1f6a1271d882a60b532acf7f0f0d033138380f28cf21a481c9401034f36b4ad81d59a1b45586d3cdad296375c0bf24600b3f6a487a466aa05c724b6a0c7a9721e9fb50be6b3585441c8b27d2800ad94752c20080a01cb8005c0014c5a37fda958d33c0c7", + "headers": [ + { + ":status": "302" + }, + { + "date": "Sat, 03 Nov 2012 13:38:05 GMT" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "P3P - policyref=\"http://www.adfusion.com/w3c/adfusion.xml\", CP=\"NON DSP COR CURa TIA\"" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "pragma": "no-cache" + }, + { + "location": "http://www.adfusion.com/AdServer/default.aspx?e=i&lid=10641&ct=" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "-1" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-length": "188" + }, + { + "set-cookie": "cid=6f010485-f507-4a4e-a485-feb7619db013; domain=.adfusion.com; expires=Wed, 01-Jan-2020 06:00:00 GMT; path=/" + } + ] + }, + { + "seqno": 205, + "wire": "880f28fab1288a1861860d19edbaf39b11f9d711aff0f0e6787c3992bc3bb6d1a7878bbbdaa34d78760367099ad52e126cd81e3f937e5187171af25741b2385bad17379bbd3f6e85f1fbe707da97cf48cd540bd85ee82197a8a9fb53079acd615106eb6afa500cada4fdd61002ca8166e32f5c0014c5a37fda9ac699e0634088f2b5761c8b48348f89ae46568e61a001583f7f04e9acf4189eac2cb07f33a535dc618e885ec2f7410cbd454b1e19231620d5b35afe69a3f9fa52f6b83f9d3ab4a9af742a6bdd7d4c9c6153271bea6adfad4dd0e853269bea70d3914d7c36a97b568534c3c54c9a77a97f06852f69dea6edf0a9af6e05356fbca63c10ff3f76035253495886a8eb10649cbfde6496df3dbf4a002a651d4a05f740a0017000b800298b46ff5f9a1d75d0620d263d4c741f71a0961ab4fd9271d882a60e1bf0acf7798624f6d5d4b27ff07b8b84842d695b05443c86aa6ff0", + "headers": [ + { + ":status": "200" + }, + { + "set-cookie": "rts_AAAA=MLuB86QsXkGiDUw6LAw6IpFSRlNUwBT4lNpFQ0QUg4OfFcQQ1VXgXlFGVpIpliI6eB4eKxBjZB19azY=; Domain=.revsci.net; Expires=Sun, 03-Nov-2013 13:38:00 GMT; Path=/" + }, + { + "x-proc-data": "pd3-bgas01-1" + }, + { + "p3p": "policyref=\"http://js.revsci.net/w3c/rsip3p.xml\", CP=\"NON PSA PSD IVA IVD OTP SAM IND UNI PUR COM NAV INT DEM CNT STA PRE OTC HEA\"" + }, + { + "server": "RSI" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "content-type": "application/javascript;charset=UTF-8" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "date": "Sat, 03 Nov 2012 13:37:59 GMT" + } + ] + }, + { + "seqno": 206, + "wire": "88768c86b19272ad78fe8e92b015c3e3d1d0f2cfd80f0d8375975bf1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "expires": "Sat Nov 03 13:37:59 UTC 2012" + }, + { + "content-encoding": "gzip" + }, + { + "p3p": "CP=\"NOI CURa ADMa DEVa TAIa OUR BUS IND UNI COM NAV INT\"" + }, + { + "content-type": "text/html" + }, + { + "content-length": "7375" + }, + { + "date": "Sat, 03 Nov 2012 13:37:59 GMT" + } + ] + }, + { + "seqno": 207, + "wire": "88cdcccbcac9e30f1faf9d29aee30c78f1e171c92da831ea5c87a58864dc5b3b96c6242ca3b684ae3457e7fc2c06f8a0d2401038d07e08983fc3c8c70f0d8369a69f0f28cf21a481c9401034f36b4ad81d59a1b45586d3cdad296375c0bf24600b3f6a487a466aa05c724b6a0c7a9721e9fb50be6b3585441c8b27d2800ad94752c20080a01cb8005c0014c5a37fda958d33c0c7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:05 GMT" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "P3P - policyref=\"http://www.adfusion.com/w3c/adfusion.xml\", CP=\"NON DSP COR CURa TIA\"" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "pragma": "no-cache" + }, + { + "location": "http://www.adfusion.com/AdServer/default.aspx?e=i&lid=10641&ct=" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "-1" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-length": "4449" + }, + { + "set-cookie": "cid=6f010485-f507-4a4e-a485-feb7619db013; domain=.adfusion.com; expires=Wed, 01-Jan-2020 06:00:00 GMT; path=/" + } + ] + }, + { + "seqno": 208, + "wire": "885f88352398ac74acb37f0f0d846dc7822fea6196dc34fd280654d27eea0801128115c69bb80654c5a37f769186b19272b025c4bb2a7f578b52756efeff6c96dc34fd28179486d99410022500e5c0357196d4c5a37f0f1395fe5b90038c6d2d248522cd11d79a04807082203f9f52848fd24a8f5889a47e561cc58197000f6496dc34fd280654d27eea0801128166e34ddc032a62d1bf4097f2b565b29325259162587421690f48cd52d59e8310c54703616c6c5583642ebb4089f2b0e9f6b12558d27fadd3dbbd0ecf24e1fdc97d6457d3433a37748aca7be5b35474245db9cefeccc3d6d43c3b2d95d7d6e17479a6820f7caf0ae05257dd081d75e91979e940e34dca58d96e370a469e9190b8b9283db24b61ea4af5152a7f57a83db261b0f527fb4085f2b10649cb8ec664a92d87a542507b6496c3d49f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "56812" + }, + { + "connection": "keep-alive" + }, + { + "date": "Sat, 03 Nov 2012 12:45:03 GMT" + }, + { + "server": "Apache/2.2.3 (CentOS)" + }, + { + "last-modified": "Sat, 18 Aug 2012 06:04:35 GMT" + }, + { + "etag": "\"5d0aba4-ddec-4c7840d06c2c0\"" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=3600" + }, + { + "expires": "Sat, 03 Nov 2012 13:45:03 GMT" + }, + { + "x-permitted-cross-domain-policies": "all" + }, + { + "age": "3177" + }, + { + "x-amz-cf-id": "Nqvl7hdh1ZID-spjM3MSj_rmvJrOblt2qYh9QKaP4AUq-J79-UBaKg==" + }, + { + "via": "1.0 f9710778d388f0645feb35b6ec48d316.cloudfront.net (CloudFront)" + }, + { + "x-cache": "Hit from cloudfront" + } + ] + }, + { + "seqno": 209, + "wire": "886196dc34fd280654d27eea0801128166e32f5c0014c5a37f5f8b497ca58e83ee3412c3569f0f0d03313733f70f28eaef03618e52471b6c81b78251caf3457df0c6f4728e46c0fb61285a6de9197dc59c719680f32fb607db095979e65a89965c73ed42f9acd615106e1a7e94032b693f7584008940b5700f5c0014c5a37fda958d33c0c7da921e919aa8172cb294893772d251a2db0abd454fd1f0768821e8a0a4498f5041408b20c9395690d614893772ff86a8eb10649cbf408caec1cd48d690d614893772ff86a8eb10649cbff47f17a7acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725ffe7f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:00 GMT" + }, + { + "content-type": "text/javascript" + }, + { + "content-length": "173" + }, + { + "connection": "keep-alive" + }, + { + "set-cookie": "v=51bfcbb530581eaf84e991b8bfad50951e1458d396-6634083950951e38834_3366; expires=Sat, 03-Nov-2012 14:08:00 GMT; path=/; domain=.effectivemeasure.net" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "server": "collection10" + }, + { + "cache-directive": "no-cache" + }, + { + "pragma-directive": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID\"" + } + ] + }, + { + "seqno": 210, + "wire": "89f1d15a839bd9ab6496d07abe940054ca3a940bef814002e001700053168dff6196dc34fd280654d27eea0801128166e32edc6df53168df0f0d0130408721eaa8a4498f5788ea52d6b0e83772ff58b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bffa", + "headers": [ + { + ":status": "204" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:37:59 GMT" + }, + { + "content-length": "0" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 211, + "wire": "88768586b19272ff5f961d75d0620d263d4c7441eafb24e3b1054c1c37e159ef54012a409419085421621ea4d87a161d141fc2d495339e447f95d7ab76ffa53160dff4a6be1bfe94d5af7e4d5a777f409419085421621ea4d87a161d141fc2d3947216c47f99bc7a925a92b6ff5597e94fc5b697b5a5424b22dc8c99fe94f90f0d8208435888a47e561cc58190ffcec5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "content-type": "application/json;charset=UTF-8" + }, + { + "access-control-allow-origin": "*" + }, + { + "access-control-allow-methods": "POST, GET, PUT, OPTIONS" + }, + { + "access-control-allow-headers": "Content-Type, X-Requested-With, *" + }, + { + "content-length": "111" + }, + { + "cache-control": "max-age=31" + }, + { + "date": "Sat, 03 Nov 2012 13:38:00 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 212, + "wire": "88c3dc5f86497ca582211fc96c96df697e94640a6a225410022500f5c13b702fa98b46ff588ca47e561cc581c13a075d71af6496df3dbf4a320535112a080169403d704edc640a62d1bf6196dc34fd280654d27eea0801128166e32f5c038a62d1bf0f0d03323937ca4084f2b563938d1f739a4523b0fe105b148ed9bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "text/css" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:27:19 GMT" + }, + { + "cache-control": "max-age=62707764" + }, + { + "expires": "Thu, 30 Oct 2014 08:27:30 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:06 GMT" + }, + { + "content-length": "297" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 213, + "wire": "88bfc96c96e4593e940054d03b141000e2816ee059b8db8a62d1bfdd0f0d0234335896a47e561cc5801f4a547588324e5837152b5e39fa98bf6496dc34fd280654d27eea0801128166e32f5c038a62d1bf4088ea52d6b0e83772ff8e49a929ed4c0107d2948fcc01705f7f1088cc52d6b4341bb97f5f87352398ac4c697f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:06 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Wed, 01 Mar 2006 15:13:56 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "43" + }, + { + "cache-control": "max-age=0, no-cache=Set-Cookie" + }, + { + "expires": "Sat, 03 Nov 2012 13:38:06 GMT" + }, + { + "keep-alive": "timeout=10, max=162" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 214, + "wire": "88cfe85f911d75d0620d263d4c795ba0fb8d04b0d5a7d56c96df3dbf4a044a65b6850400894082e005702fa98b46ff588ca47e561cc581b65a79d759776496df697e940b6a65b6850400b4a05bb8205c134a62d1bf6196dc34fd280654d27eea0801128166e32f5c03aa62d1bf0f0d8375f71fd6c9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 12 Jul 2012 10:02:19 GMT" + }, + { + "cache-control": "max-age=53487737" + }, + { + "expires": "Tue, 15 Jul 2014 15:20:24 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "content-length": "7969" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 215, + "wire": "88d46c96c361be940094d27eea0801128172e085719754c5a37feeda0f0d8369e6c5ec588ca47e561cc581c13efb6eb8e76496dd6d5f4a004a693f750400b4a05cb8276e32ca98b46fc1d9cc", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Fri, 02 Nov 2012 16:22:37 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "4852" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=62995766" + }, + { + "expires": "Sun, 02 Nov 2014 16:27:33 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 216, + "wire": "88d7f0eeccdc6c96c361be940094d27eea0801128266e059b806d4c5a37f0f0d8371b69e588ca47e561cc581c6402032d33f6496dd6d5f4a004a693f750400b4a099b8176e040a62d1bfc4dc", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 23:13:05 GMT" + }, + { + "content-length": "6548" + }, + { + "cache-control": "max-age=63020343" + }, + { + "expires": "Sun, 02 Nov 2014 23:17:10 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 217, + "wire": "88daf3f1df6c96df3dbf4a002a693f75040089403d700d5c0bea62d1bf588ca47e561cc581c13cf01965ff6496dc34fd2800a9a4fdd41002d2807ae099b8d38a62d1bfc70f0d83682d35df", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 01 Nov 2012 08:04:19 GMT" + }, + { + "cache-control": "max-age=62880339" + }, + { + "expires": "Sat, 01 Nov 2014 08:23:46 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "content-length": "4144" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 218, + "wire": "88ddf6f4e26c96df3dbf4a002a693f7504008940b57000b8d894c5a37f0f0d8365d703588ca47e561cc581c13e00804dff6496dc34fd2800a9a4fdd41002d2816ae01eb8c894c5a37fcae2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 01 Nov 2012 14:00:52 GMT" + }, + { + "content-length": "3761" + }, + { + "cache-control": "max-age=62901025" + }, + { + "expires": "Sat, 01 Nov 2014 14:08:32 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 219, + "wire": "88e0f76c96df3dbf4a002a693f7504008940bd7042b806d4c5a37f0f0d8371c003588ca47e561cc581c13e171e103f6496dc34fd2800a9a4fdd41002d2817ae321b8d3aa62d1bfcde5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Thu, 01 Nov 2012 18:22:05 GMT" + }, + { + "content-length": "6600" + }, + { + "cache-control": "max-age=62916820" + }, + { + "expires": "Sat, 01 Nov 2014 18:31:47 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 220, + "wire": "887689bf7b3e65a193777b3fd20f0d03343534e9ce", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "454" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + } + ] + }, + { + "seqno": 221, + "wire": "88e47b8b84842d695b05443c86aa6f5f88352398ac74acb37fdbeb6c96c361be940094d27eea080112816ae32fdc138a62d1bf0f0d8371e6c3588ca47e561cc581c13ef3ec883f6496dd6d5f4a004a693f750400b4a05ab8d02e01e53168dfd3eb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 14:39:26 GMT" + }, + { + "content-length": "6851" + }, + { + "cache-control": "max-age=62989321" + }, + { + "expires": "Sun, 02 Nov 2014 14:40:08 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 222, + "wire": "88e9c2c1ee6c96df3dbf4a09b535112a080112810dc13b71b714c5a37f0f0d836da087588ca47e561cc581c132203af0bf6496dc34fd2826d4d444a82005a5040b8dbb71a7d4c5a37fd6ee", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 25 Oct 2012 11:27:56 GMT" + }, + { + "content-length": "5411" + }, + { + "cache-control": "max-age=62320782" + }, + { + "expires": "Sat, 25 Oct 2014 20:57:49 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 223, + "wire": "48826402768c86b19272ad78fe8e92b015c30f1fffbf029d29aee30c1a9997a4b21875d05f2b90f4b043d492d49600c05e7c2e319f7c5f9a33c5b4692ef1c74162dc4b0f4506aa6c651c941aa2c5aedb2ba0b0d961fc222da521ec9339fc222744f797c1101b004225fa23fca6b27580742651d94af496430eba0be5721e954584722a2c24eaa8b085e7c2e2c165969d12cc89e71c59e559c34d69559bef38275c77e29ad303ce09d71df8a6bee48274a6bb8c30391790f6c741494189d57a8a96094189d5566eceab37fbbcc332084c32c018544360eab3744e34d3416dd79566e81602acdd02acdd0be17dc784e2acdd65a6da59d13cc3e0559bad3ef8196de0b0d3ef3edb427d80aacdd559baabb80fd7baacdf559ba8a0e9559bf4147216c8ce3b24559ba8f6ab37dd13de5f02a2bcfba0f2e38a8af3ee83cbe054579f741e44d81566ea0a44d4ab37ea2f842acdd227d565559be6aa42f9559bb517c21566ff83d9448ab376c2ca5b2c2d8ab37ea2f84783d9448341862005f032cbaab37643d23354ab37fc78f0bc7191721d7b7aaacddb0b296cb0b64521e919aa559bfe3c785e38c8b90ebdbd5566ed8832acdfca079d78310401644ab376c419566fe503cebc18820704f2acdd55dc084110ab37d5665f0f0d0130d85886a8eb2127b0bf4085aec1cd48ff86a8eb10649cbf6401307f38bcacf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9af7427535eebe753570daa64d37d4e1a72297b568534c3c7f9f0f28c9a4fd0ecc0179f0b971913ce38c0590003704275c6fed42f9acd615106eb6afa504b693f758400b4a05cb8db3700fa98b46ffb52b1a67818fb5243d2335502f496430eba0be5721e9fb", + "headers": [ + { + ":status": "302" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "location": "http://img.mediaplex.com/content/0/18916/LT_XML_RateTable_ScrollingHeadline_PurpleArrows_RecordLows_728x90_050112.js?mpck=altfarm.mediaplex.com%2Fad%2Fck%2F18916-133472-32866-8%3Fmpt%3D862767&mpt=862767&mpvc=http://ad.doubleclick.net/click%3Bh%3Dv8/3d22/3/0/%2a/o%3B264441578%3B0-0%3B0%3B19196826%3B3454-728/90%3B49903581/49895429/1%3B%3B%7Eokv%3D%3Bslot%3Dleaderboard%3Bsz%3D728x90%2C970x66%2C970x90%2C970x250%3Bsectn%3Dnews%3Bctype%3Dindex%3Bnews%3Dworld%3Breferrer%3Dnewsworldasia20190337%3Bdomain%3Dwww.bbc.co.uk%3Breferrer_domain%3Dwww.bbc.co.uk%3Brsi%3DJ08781_10132%3Brsi%3DJ08781_10628%3B%7Esscs%3D%3f" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "cache-control": "no-store" + }, + { + "pragma": "no-cache" + }, + { + "expires": "0" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR PSAo PSDo OUR IND UNI COM NAV\"" + }, + { + "set-cookie": "mojo3=18916:32866/13001:22765; expires=Sun, 2-Nov-2014 16:53:09 GMT; path=/; domain=.mediaplex.com;" + } + ] + }, + { + "seqno": 224, + "wire": "88dcf26495dc34fd2800a994752820000a0017000b800298b46fc15886a8eb10649cbf7f01caacf4189eac2cb07f33a535dc618f1e3c2f5164424695c87a58f0c918ad9ad7f34d1fcfd297b5c1fce9d5914bfbb5a97b56d534e4bea6bdd0a90dfd0a6ae1b54c9a6fa9a61e2a5ed5a3f90f0d0234337f26842507417fe5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 01 Jan 2000 00:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.nedstat.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND NAV COM\"" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 225, + "wire": "88e0f66c96df697e940054d03f4a080112817ee05bb8d014c5a37f0f1394fe4410be40ac16c4e2cd46594ae36eb6d4a007f352848fd24a8f0f0d8371f7817f2a8749a929ed4c0dffe9e7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Tue, 01 May 2012 19:15:40 GMT" + }, + { + "etag": "\"2119c1-1526-4befe65754f00\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "6980" + }, + { + "keep-alive": "timeout=5" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "application/x-javascript" + } + ] + }, + { + "seqno": 226, + "wire": "88f96496dc34fd280654d27eea0801128166e32f5c036a62d1bff85f87497ca589d34d1f0f0d847c0e38e76196dc34fd280654d27eea0801128166e32f5c036a62d1bf7f0588ea52d6b0e83772ff408af2b10649cab0c8931eaf044d4953534088f2b10649cab0e62f0130589aaec3771a4bf4a523f2b0e62c00fa529b5095ac2f71d0690692ff4089f2b511ad51c8324e5f834d96977b05582d43444e", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 03 Nov 2012 13:38:05 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "text/html" + }, + { + "content-length": "90666" + }, + { + "date": "Sat, 03 Nov 2012 13:38:05 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-action": "MISS" + }, + { + "x-cache-age": "0" + }, + { + "cache-control": "private, max-age=0, must-revalidate" + }, + { + "x-lb-nocache": "true" + }, + { + "vary": "X-CDN" + } + ] + }, + { + "seqno": 227, + "wire": "88768586b19272ff6c96e4593e940bea6a225410021500edc13d7197d4c5a37ff9dd5a839bd9ab0f0d840bccb6ffdd588ca47e561cc581b132d3ecb2f76496e4593e940094cb6d0a0801694086e01db806d4c5a37ff1c8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Wed, 19 Oct 2011 07:28:39 GMT" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "18359" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=52349338" + }, + { + "expires": "Wed, 02 Jul 2014 11:07:05 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 228, + "wire": "88c2e0dfc06c96c361be940094d27eea080112817ae05fb82694c5a37f588ca47e561cc581c6400136cb5f6496dd6d5f4a004a693f750400b4a05eb8205c1054c5a37ff40f0d836dd7c3cb4084f2b563938d1f739a4523b0fe105b148ed9bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 18:19:24 GMT" + }, + { + "cache-control": "max-age=63002534" + }, + { + "expires": "Sun, 02 Nov 2014 18:20:21 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "content-length": "5791" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 229, + "wire": "88c66c96dc34fd280654d27eea080112807ee320b8d3aa62d1bfe5c50f0d836c0cb7e4588ca47e561cc581c640dbac801f6496d07abe94032a693f750400b4a01fb8cb3700ea98b46ff8cfc1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Sat, 03 Nov 2012 09:30:47 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "5035" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=63057300" + }, + { + "expires": "Mon, 03 Nov 2014 09:33:07 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 230, + "wire": "88c9e7e6c76c96c361be940094d27eea0801128072e36ddc684a62d1bf588ca47e561cc581c13ee05b743f6497dd6d5f4a004a693f750400b4a01cb8dbb719794c5a37fffb0f0d83704e39d2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 06:55:42 GMT" + }, + { + "cache-control": "max-age=62961571" + }, + { + "expires": "Sun, 02 Nov 2014 06:57:38 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "content-length": "6266" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 231, + "wire": "88cce96c96e4593e94642a6a225410022502ddc6d9b8cbaa62d1bf0f0d8369d6dd588ca47e561cc581c13c203ef39f6496c361be94642a6a22541002d2816ee36d5c65953168df6196dc34fd280654d27eea0801128166e32f5c03aa62d1bfd6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Wed, 31 Oct 2012 15:53:37 GMT" + }, + { + "content-length": "4757" + }, + { + "cache-control": "max-age=62820986" + }, + { + "expires": "Fri, 31 Oct 2014 15:54:33 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 232, + "wire": "88d0eeedce6c96df3dbf4a002a693f75040089403f7001b8d094c5a37f588ca47e561cc581c13cf09d083f6496dc34fd2800a9a4fdd41002d2807ee019b81754c5a37fc10f0d836d97dcd9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 01 Nov 2012 09:01:42 GMT" + }, + { + "cache-control": "max-age=62882710" + }, + { + "expires": "Sat, 01 Nov 2014 09:03:17 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "content-length": "5396" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 233, + "wire": "88d3f1f0cbd16c96c361be940094d27eea080112807ae01fb8c814c5a37f0f0d8369d6df588ca47e561cc581c13ee36fb2d76496dd6d5f4a004a693f750400b4a01eb8105c1054c5a37fc4dc", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:09:30 GMT" + }, + { + "content-length": "4759" + }, + { + "cache-control": "max-age=62965934" + }, + { + "expires": "Sun, 02 Nov 2014 08:10:21 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 234, + "wire": "88d6f4f3d46c96c361be940094d27eea0801128166e09ab81754c5a37f0f0d83644017588ca47e561cc581c13ef3ee01df6497dd6d5f4a004a693f750400b4a05ab8d3571b694c5a37ffc7df", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 13:24:17 GMT" + }, + { + "content-length": "3202" + }, + { + "cache-control": "max-age=62989607" + }, + { + "expires": "Sun, 02 Nov 2014 14:44:54 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 235, + "wire": "88d96c96c361be940094d27eea0801128166e003704da98b46ffd2f8d80f0d83680217f7588ca47e561cc581c13ef32e09df6496dd6d5f4a004a693f750400b4a059b806ee05b53168df6196dc34fd280654d27eea0801128166e32f5c03ca62d1bfe3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Fri, 02 Nov 2012 13:01:25 GMT" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "4022" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=62983627" + }, + { + "expires": "Sun, 02 Nov 2014 13:05:15 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:08 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 236, + "wire": "88ddfbfadb6c96c361be940094d27eea080112807ae36d5c6db53168df588ca47e561cc581c13ee802f03f6496dd6d5f4a004a693f750400b4a01fb820dc03ca62d1bfc10f0d8369e6d9e6d8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 08:54:55 GMT" + }, + { + "cache-control": "max-age=62970180" + }, + { + "expires": "Sun, 02 Nov 2014 09:21:08 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:08 GMT" + }, + { + "content-length": "4853" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 237, + "wire": "88e06c96df3dbf4a002a693f7504008940b371a6ae01a53168df7b8b84842d695b05443c86aa6fe00f0d83644f3f5f87352398ac4c697f588ca47e561cc581c13e00ba017f6496dc34fd2800a9a4fdd41002d2816ae05fb8d814c5a37fc6ebdd", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Thu, 01 Nov 2012 13:44:04 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "3289" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "max-age=62901702" + }, + { + "expires": "Sat, 01 Nov 2014 14:19:50 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:08 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 238, + "wire": "88e5c15f88352398ac74acb37fdee46c96df3dbf4a002a693f7504008940b971b6ee002a62d1bf0f0d8374206f588ca47e561cc581c13e1081f67f6496dc34fd2800a9a4fdd41002d28172e36e5c1014c5a37fd7ef", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 01 Nov 2012 16:55:01 GMT" + }, + { + "content-length": "7105" + }, + { + "cache-control": "max-age=62911093" + }, + { + "expires": "Sat, 01 Nov 2014 16:56:20 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 239, + "wire": "88e9c5c1e76c96c361be940b4a6e2d6a0801128076e36edc65c53168df588ca47e561cc581b79d644cb6ef6496dd6d5f4a05a53716b50400b4a01eb8105c69b53168dfcd0f0d8365f701f2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 14 Sep 2012 07:57:36 GMT" + }, + { + "cache-control": "max-age=58732357" + }, + { + "expires": "Sun, 14 Sep 2014 08:10:45 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:08 GMT" + }, + { + "content-length": "3960" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 240, + "wire": "88ecc46c96df697e94105486d99410022500edc65cb8cbaa62d1bf0f0d8365f69e588ca47e561cc581b71c6dc7441f6496df3dbf4a082a436cca080169403b71972e34fa98b46fd0f5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Tue, 21 Aug 2012 07:36:37 GMT" + }, + { + "content-length": "3948" + }, + { + "cache-control": "max-age=56656721" + }, + { + "expires": "Thu, 21 Aug 2014 07:36:49 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:08 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 241, + "wire": "88ef6c96df3dbf4a01f5340ec5040038a08371915c642a62d1bf0f1392fe5f95a048b32359a015f71f90426df203f9fb5f911d75d0620d263d4c795ba0fb8d04b0d5a7cdefd20f0d023633f7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Thu, 09 Mar 2006 21:32:31 GMT" + }, + { + "etag": "\"9f40d-3a-40e969d2259c0\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:08 GMT" + }, + { + "content-length": "63" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 242, + "wire": "88f16c96d07abe94036a681d8a0801128115c0b7704ca98b46ffcaeacef00f0d846df7db7b588ca47e561cc581a101d0bce3ff6496e4593e94036a681d8a080169408ae05bb8db8a62d1bfe2fa", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Mon, 05 Mar 2012 12:15:23 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "59958" + }, + { + "cache-control": "max-age=42071869" + }, + { + "expires": "Wed, 05 Mar 2014 12:15:56 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 243, + "wire": "88f45f8b497ca58e83ee3412c3569f4085aec1cd48ff000f0d033437395888a47e561cc581907fd8fd", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "content-type": "text/javascript" + }, + { + "pragma": "" + }, + { + "content-length": "479" + }, + { + "cache-control": "max-age=30" + }, + { + "date": "Sat, 03 Nov 2012 13:38:08 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 244, + "wire": "887689bf7b3e65a193777b3fc50f0d83134fb3f66196dc34fd280654d27eea0801128166e32f5c03ea62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "2493" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:09 GMT" + } + ] + }, + { + "seqno": 245, + "wire": "88bfc60f0d826441f7be", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "321" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:09 GMT" + } + ] + }, + { + "seqno": 246, + "wire": "884003703370fdacf4189eac2cb07f33a535dc61898e79a828e442f32f21ed8e82928313aaf5152c56398a39189895455b35c4bf9a68fe7e94bdae0fe6f70da3521bfa06a5fc1c46a6f8721d4d7ba13a9af75f3a9ab86d53269bea70d3914d7c36a9934ef52fe0d0a6edf0a9af6e052f6ad0a69878a9ab7de534eac8a5fddad4bdab6ff3bf7f0386a8eb10649cbf6496c361be940054ca3a940bef814002e001700053168dff5892a8eb10649cbf4a536a12b585ee3a0d20d25fca4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cbc40f0d03333633408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f5a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "p3p": "policyref=\"http://googleads.g.doubleclick.net/pagead/gcn_p3p_.xml\", CP=\"CURa ADMa DEVa TAIo PSAo PSDo OUR IND UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "date": "Sat, 03 Nov 2012 13:38:09 GMT" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "application/x-javascript" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-length": "363" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 247, + "wire": "88c6cd0f0d821045bec5", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "212" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:09 GMT" + } + ] + }, + { + "seqno": 248, + "wire": "488264026196dc34fd280654d27eea0801128166e32f5c0b4a62d1bf768dd06258741e54ad9326e61c5c1f7f08c3d6ceb51652b3d0627ab0b2c1fcce94d771863c78f0b8e496d418f52e43d2c78648c0e496d418f52fe69a3f9fa52f6b83f9d3ab4a97f76b52f6adaa5ee1b46a6fc90ff34089f2b567f05b0b22d1fa868776b5f4e0df408cf2b0d15d454addcb620c7abf8712e05db03a277fc90f1faf9d29aee30c78f1e171c92da831ea5c87a58864dc5b3b96c6242ca3b684ae3457e7fc2c06f8a0d2401038d07e08983f5886a8eb10649cbf64022d315f92497ca589d34d1f6a1271d882a60b532acf7f0f0d033138380f28cf21a481c9401034f36b4ad81d59a1b45586d3cdad296375c0bf24600b3f6a487a466aa05c724b6a0c7a9721e9fb50be6b3585441c8b27d2800ad94752c20080a01cb8005c0014c5a37fda958d33c0c7", + "headers": [ + { + ":status": "302" + }, + { + "date": "Sat, 03 Nov 2012 13:38:14 GMT" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "P3P - policyref=\"http://www.adfusion.com/w3c/adfusion.xml\", CP=\"NON DSP COR CURa TIA\"" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "pragma": "no-cache" + }, + { + "location": "http://www.adfusion.com/AdServer/default.aspx?e=i&lid=10641&ct=" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "-1" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-length": "188" + }, + { + "set-cookie": "cid=6f010485-f507-4a4e-a485-feb7619db013; domain=.adfusion.com; expires=Wed, 01-Jan-2020 06:00:00 GMT; path=/" + } + ] + }, + { + "seqno": 249, + "wire": "88c5c4c3c2c1cc0f1faf9d29aee30c78f1e171c92da831ea5c87a58864dc5b3b96c6242ca3b684ae3457e7fc2c06f8a0d2401038d07e08983fc0bfbe0f0d8369a69f0f28cf21a481c9401034f36b4ad81d59a1b45586d3cdad296375c0bf24600b3f6a487a466aa05c724b6a0c7a9721e9fb50be6b3585441c8b27d2800ad94752c20080a01cb8005c0014c5a37fda958d33c0c7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:14 GMT" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "P3P - policyref=\"http://www.adfusion.com/w3c/adfusion.xml\", CP=\"NON DSP COR CURa TIA\"" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "pragma": "no-cache" + }, + { + "location": "http://www.adfusion.com/AdServer/default.aspx?e=i&lid=10641&ct=" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "-1" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-length": "4449" + }, + { + "set-cookie": "cid=6f010485-f507-4a4e-a485-feb7619db013; domain=.adfusion.com; expires=Wed, 01-Jan-2020 06:00:00 GMT; path=/" + } + ] + }, + { + "seqno": 250, + "wire": "88cd5f96497ca58e83ee3412c3569fb50938ec4153070df8567bca59871a52324f496a4fc9d0768320e52f5885aec3771a4b0f0d830b2fb9cc", + "headers": [ + { + ":status": "200" + }, + { + "p3p": "policyref=\"http://googleads.g.doubleclick.net/pagead/gcn_p3p_.xml\", CP=\"CURa ADMa DEVa TAIo PSAo PSDo OUR IND UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "text/javascript; charset=UTF-8" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-disposition": "attachment" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:09 GMT" + }, + { + "server": "cafe" + }, + { + "cache-control": "private" + }, + { + "content-length": "1396" + }, + { + "x-xss-protection": "1; mode=block" + } + ] + }, + { + "seqno": 251, + "wire": "880f28fbb1288a1861860d19edbb336ffe78a18c1835070f0db7616490d1353523fdfef44f0aba7fbd50ddb077f4bd1defdf77e9e93e5c53c79a1f38395d269cfeb5f2e0f51ac7bbdef2b2007da97cf48cd540bd85ee82197a8a9fb53079acd615106eb6afa500cada4fdd61002ca8166e32f5c03ea62d1bfed4d634cf031f4088f2b5761c8b48348f89ae46568e61a00d2c1f7f09e9acf4189eac2cb07f33a535dc618e885ec2f7410cbd454b1e19231620d5b35afe69a3f9fa52f6b83f9d3ab4a9af742a6bdd7d4c9c6153271bea6adfad4dd0e853269bea70d3914d7c36a97b568534c3c54c9a77a97f06852f69dea6edf0a9af6e05356fbca63c10ff3f7603525349c7d36496df3dbf4a002a651d4a05f740a0017000b800298b46ff5f9a1d75d0620d263d4c741f71a0961ab4fd9271d882a60e1bf0acf7798624f6d5d4b27fd1efd8", + "headers": [ + { + ":status": "200" + }, + { + "set-cookie": "rts_AAAA=MLuBg59Xwl/EEO1FURBA3cAlgmns+ZjtUnj+OABraDN8bCZzDmjhJGhbKAxEWBcNLyPWU8lPaSzTe300; Domain=.revsci.net; Expires=Sun, 03-Nov-2013 13:38:09 GMT; Path=/" + }, + { + "x-proc-data": "pd3-bgas04-1" + }, + { + "p3p": "policyref=\"http://js.revsci.net/w3c/rsip3p.xml\", CP=\"NON PSA PSD IVA IVD OTP SAM IND UNI PUR COM NAV INT DEM CNT STA PRE OTC HEA\"" + }, + { + "server": "RSI" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "content-type": "application/javascript;charset=UTF-8" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "date": "Sat, 03 Nov 2012 13:38:09 GMT" + } + ] + }, + { + "seqno": 252, + "wire": "88d8dc0f0d03313733408721eaa8a4498f5788ea52d6b0e83772ff0f28eaef0481148db3295a8e465f03efc6191f7c31bd1ca391b03ed84a169b7a465f71671c65a03ccbed81f6c25682f32d44cb2e39f6a17cd66b0a88370d3f4a0195b49fbac20044a05ab807ae01f53168dff6a5634cf031f6a487a466aa05cb2ca5224ddcb49468b6c2af5153cb640130768821e8a0a4498f5041408b20c9395690d614893772ff86a8eb10649cbf408caec1cd48d690d614893772ff86a8eb10649cbfdb7f08a7acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725ffe7f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:09 GMT" + }, + { + "content-type": "text/javascript" + }, + { + "content-length": "173" + }, + { + "connection": "keep-alive" + }, + { + "set-cookie": "v=d12d53fe4bd39099b1d991b8bfad50951e1458d396-6634083950951e41834_3366; expires=Sat, 03-Nov-2012 14:08:09 GMT; path=/; domain=.effectivemeasure.net" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "server": "collection10" + }, + { + "cache-directive": "no-cache" + }, + { + "pragma-directive": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID\"" + } + ] + }, + { + "seqno": 253, + "wire": "88768586b19272fff6f5d86c96d07abe9403ea65b68504008940b7700fdc1054c5a37f588ca47e561cc581b13ee3ce3ccf6496e4593e9403ea65b6850400b4a05bb807ee32253168dfe20f0d84085f65dfc74084f2b563938d1f739a4523b0fe105b148ed9bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/gif" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Mon, 09 Jul 2012 15:09:21 GMT" + }, + { + "cache-control": "max-age=52968683" + }, + { + "expires": "Wed, 09 Jul 2014 15:09:32 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:09 GMT" + }, + { + "content-length": "11937" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 254, + "wire": "89ebfadc6496d07abe940054ca3a940bef814002e001700053168dffe40f0d0130c958b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfe3", + "headers": [ + { + ":status": "204" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:09 GMT" + }, + { + "content-length": "0" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 255, + "wire": "88c46c96df697e94640a6a225410022500f5c13971a654c5a37f5f87352398ac5754dfc27b8b84842d695b05443c86aa6fe10f0d830b6fb5588ca47e561cc581c13a075c7dff6496df3dbf4a320535112a080169403d704e5c13ca62d1bfeacf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:26:43 GMT" + }, + { + "content-type": "image/png" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1594" + }, + { + "cache-control": "max-age=62707699" + }, + { + "expires": "Thu, 30 Oct 2014 08:26:28 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:09 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 256, + "wire": "88c96c96d07abe940b8a65b6850400894102e36d5c0b4a62d1bf0f1395fe5c91c209959e0b8459a2352bc3095c69f781fcff52848fd24a8f0f0d846590b22f5f88352398ac74acb37f6196dc34fd280654d27eea0801128166e32f5c03ca62d1bfd3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Mon, 16 Jul 2012 20:54:14 GMT" + }, + { + "etag": "\"6d6c23-816c-4c4f8a1e64980\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "33132" + }, + { + "content-type": "image/jpeg" + }, + { + "date": "Sat, 03 Nov 2012 13:38:08 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 257, + "wire": "886196dc34fd280654d27eea0801128166e32f5c1094c5a37fce6495dc34fd2800a994752820000a0017000b800298b46feee27f11caacf4189eac2cb07f33a535dc618f1e3c2f5164424695c87a58f0c918ad9ad7f34d1fcfd297b5c1fce9d5914bfbb5a97b56d534e4bea6bdd0a90dfd0a6ae1b54c9a6fa9a61e2a5ed5a3f90f0d0234337f17842507417f5f87352398ac4c697f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:22 GMT" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 01 Jan 2000 00:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.nedstat.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND NAV COM\"" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 258, + "wire": "88f4fb0f0d03333137ec6196dc34fd280654d27eea0801128166e32f5c132a62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "317" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + } + ] + }, + { + "seqno": 259, + "wire": "88f55f911d75d0620d263d4c795ba0fb8d04b0d5a70f0d8308842feebf", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "1222" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + } + ] + }, + { + "seqno": 260, + "wire": "88c1bfeb7f03a1bdae0fe74eac8a5fddad4bdab6a9af7427535eebe753570daa5de1b94d5bef7f3f54012aeb5f87497ca589d34d1f0f0d8369c6430f28c7d7b6b241ff31d9cfbe3bf883703ff3febee43d2335500e442f59cd526c3d142e43d3f6a5634cf031f6a17cd66b0a88341eafa500cada4fdd61002d28166e32f5c132a62d1bfeff", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "CP=\"NOI DSP COR PSAo PSDo OUR BUS OTC\"" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-type": "text/html" + }, + { + "content-length": "4631" + }, + { + "set-cookie": "PRpc=|HrYvHDG1:1|#;domain=ads.pointroll.com; path=/; expires=Mon, 03-Nov-2014 13:38:23 GMT;" + } + ] + }, + { + "seqno": 261, + "wire": "88d76c96c361be940094d27eea0801128115c659b820a98b46ffcad4cff20f0d840baf32ff588ca47e561cc581c13ef3af361f6496dd6d5f4a004a693f750400b4a05ab816ee36ca98b46fcae0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Fri, 02 Nov 2012 12:33:21 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "17839" + }, + { + "cache-control": "max-age=62987851" + }, + { + "expires": "Sun, 02 Nov 2014 14:15:53 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:22 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 262, + "wire": "88dad1ccf46c96c361be940094d27eea0801128015c6c371b694c5a37f588ca47e561cc581c13ed38f899f6496dd6d5f4a004a693f750400b4a00571b66e34da98b46fcd0f0d8369e13be3d9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 02:51:54 GMT" + }, + { + "cache-control": "max-age=62946923" + }, + { + "expires": "Sun, 02 Nov 2014 02:53:45 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:22 GMT" + }, + { + "content-length": "4827" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 263, + "wire": "88dd6c96c361be940094d27eea080112816ee34f5c682a62d1bfd5f80f0d836de7c3d0588ca47e561cc581c13efb2d3c0f6496dd6d5f4a004a693f750400b4a05bb8d3f71a1298b46fd0e6dc", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Fri, 02 Nov 2012 15:48:41 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "5891" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=62993480" + }, + { + "expires": "Sun, 02 Nov 2014 15:49:42 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:22 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 264, + "wire": "88e0d7d2dcfa6c96c361be940094d27eea0801128105c69ab80694c5a37f0f0d8365c71a588ca47e561cc581c13eeb6c85df6496dd6d5f4a004a693f750400b4a04171a72e36fa98b46fd3e9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 10:44:04 GMT" + }, + { + "content-length": "3664" + }, + { + "cache-control": "max-age=62975317" + }, + { + "expires": "Sun, 02 Nov 2014 10:46:59 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:22 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 265, + "wire": "88e36c96e4593e94642a6a225410022502cdc03d7190a98b46ffe0db5a839bd9ab0f0d836db79fd7588ca47e561cc581c13c165a79df6496c361be94642a6a22541002d28166e34fdc69f53168dfd7ed", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Wed, 31 Oct 2012 13:08:31 GMT" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "5589" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=62813487" + }, + { + "expires": "Fri, 31 Oct 2014 13:49:49 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:22 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 266, + "wire": "88e7ded9e3c06c96e4593e94642a6a2254100225021b8d3b704e298b46ff0f0d8371d79d588ca47e561cc581c13c07196dbf6496c361be94642a6a22541002d2810dc6c171b754c5a37fdaf0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Wed, 31 Oct 2012 11:47:26 GMT" + }, + { + "content-length": "6787" + }, + { + "cache-control": "max-age=62806355" + }, + { + "expires": "Fri, 31 Oct 2014 11:50:57 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:22 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 267, + "wire": "88d5ea6c96e4593e940054d03b141000e2816ee059b8db8a62d1bfde0f0d0234335896a47e561cc5801f4a547588324e5837152b5e39fa98bf6496dc34fd280654d27eea0801128166e32f5c132a62d1bf4088ea52d6b0e83772ff8e49a929ed4c0107d2948fcc016c1f7f1c88cc52d6b4341bb97fdb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Wed, 01 Mar 2006 15:13:56 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "43" + }, + { + "cache-control": "max-age=0, no-cache=Set-Cookie" + }, + { + "expires": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "keep-alive": "timeout=10, max=150" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 268, + "wire": "88dfef588eaec3771a4bf4a523f2b0e62c0c834089f2b511ad51c8324e5f834d96975f8b497ca58e83ee3412c3569f0f0d0234377f038749a929ed4c0d37c2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:22 GMT" + }, + { + "server": "Apache" + }, + { + "cache-control": "private, max-age=30" + }, + { + "x-lb-nocache": "true" + }, + { + "content-type": "text/javascript" + }, + { + "content-length": "47" + }, + { + "keep-alive": "timeout=45" + }, + { + "connection": "Keep-Alive" + } + ] + }, + { + "seqno": 269, + "wire": "887689bf7b3e65a193777b3fde0f0d83089917cddf", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "1232" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + } + ] + }, + { + "seqno": 270, + "wire": "88e1df768dd06258741e54ad9326e61c5c1fdedd4089f2b567f05b0b22d1fa868776b5f4e0dfdd0f0d8369c6dd0f28d0d7b6b241ff31d9cfc63bf881703ff31d9cfbe3bf883703ff3febee43d2335500e442f59cd526c3d142e43d3f6a5634cf031f6a17cd66b0a88341eafa500cada4fdd61002d28166e32f5c132a62d1bfef", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "CP=\"NOI DSP COR PSAo PSDo OUR BUS OTC\"" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-type": "text/html" + }, + { + "content-length": "4657" + }, + { + "set-cookie": "PRpc=|HrYwHDG0:1|HrYvHDG1:1|#;domain=ads.pointroll.com; path=/; expires=Mon, 03-Nov-2014 13:38:23 GMT;" + } + ] + }, + { + "seqno": 271, + "wire": "88f66c96df3dbf4a09b535112a0801128115c6dab8cb4a62d1bfe9f3eed00f0d83704f3f588ca47e561cc581c109f0bce07f6496dc34fd2826d4d444a82005a5022b8db9700d298b46ffe47f0988ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Thu, 25 Oct 2012 12:54:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "6289" + }, + { + "cache-control": "max-age=62291861" + }, + { + "expires": "Sat, 25 Oct 2014 12:56:04 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 272, + "wire": "88faf1ecd36c96df3dbf4a002a693f75040089403771b7ee36053168df588ca47e561cc581c13ce85f71cf6496dc34fd2800a9a4fdd41002d28072e01ab827d4c5a37fe80f0d83680e87c1f9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 01 Nov 2012 05:59:50 GMT" + }, + { + "cache-control": "max-age=62871966" + }, + { + "expires": "Sat, 01 Nov 2014 06:04:29 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "content-length": "4071" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 273, + "wire": "88768586b19272ff6c96df3dbf4a002a693f75040089403f7197ee32fa98b46ff6d80f0d8371e705f1588ca47e561cc581c13cf36069bf6496dc34fd2800a9a4fdd41002d2807ee342b82794c5a37fecc54084f2b563938d1f739a4523b0fe105b148ed9bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Thu, 01 Nov 2012 09:39:39 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "6862" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=62885045" + }, + { + "expires": "Sat, 01 Nov 2014 09:42:28 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 274, + "wire": "88c26c96d07abe94134a6e2d6a0801128115c69bb8d38a62d1bfbffadc0f0d8368016bf5588ca47e561cc581b7dc089e69bf6496e4593e94134a6e2d6a080169408ae34ddc69e53168dff0c9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Mon, 24 Sep 2012 12:45:46 GMT" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "4014" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=59612845" + }, + { + "expires": "Wed, 24 Sep 2014 12:45:48 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 275, + "wire": "88cfef0f0d03333630def0", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "360" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + } + ] + }, + { + "seqno": 276, + "wire": "88c5fcf7c1de6c96c361be940094d27eea080112806ee0457196d4c5a37f0f0d8371d745588ca47e561cc581c13edb6f36ef6496dd6d5f4a004a693f750400b4a01bb8215c680a62d1bff3cc", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 05:12:35 GMT" + }, + { + "content-length": "6772" + }, + { + "cache-control": "max-age=62955857" + }, + { + "expires": "Sun, 02 Nov 2014 05:22:40 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 277, + "wire": "887f32fdacf4189eac2cb07f33a535dc61898e79a828e442f32f21ed8e82928313aaf5152c56398a39189895455b35c4bf9a68fe7e94bdae0fe6f70da3521bfa06a5fc1c46a6f8721d4d7ba13a9af75f3a9ab86d53269bea70d3914d7c36a9934ef52fe0d0a6edf0a9af6e052f6ad0a69878a9ab7de534eac8a5fddad4bdab6ff3f44085aec1cd48ff86a8eb10649cbf6496c361be940054ca3a940bef814002e001700053168dff5892a8eb10649cbf4a536a12b585ee3a0d20d25ff64090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cbd70f0d03353438408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275fe7", + "headers": [ + { + ":status": "200" + }, + { + "p3p": "policyref=\"http://googleads.g.doubleclick.net/pagead/gcn_p3p_.xml\", CP=\"CURa ADMa DEVa TAIo PSAo PSDo OUR IND UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "application/x-javascript" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-length": "548" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 278, + "wire": "88d8f80f0d03353435e76196dc34fd280654d27eea0801128166e32f5c134a62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "545" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + } + ] + }, + { + "seqno": 279, + "wire": "88d9f90f0d03353435e8be", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "545" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + } + ] + }, + { + "seqno": 280, + "wire": "887b8b84842d695b05443c86aa6fe95f88352398ac74acb37f6c96df697e940054c258d410021502e5c69bb81754c5a37f6196c361be940094d27eea080112817ee34fdc1054c5a37f6496dc34fd280654d27eea080112817ee34fdc1054c5a37fc5768344b2970f0d8369a69dc5558471a0b4cf5890aed8e8313e94a47e561cc581e71a003f", + "headers": [ + { + ":status": "200" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Tue, 01 Feb 2011 16:45:17 GMT" + }, + { + "date": "Fri, 02 Nov 2012 19:49:21 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 19:49:21 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "4447" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "64143" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 281, + "wire": "885f87352398ac4c697f6c96df697e94136a6e2d6a0801128205c65db810a98b46ff6196c361be940094d27eea0801128205c0b571b694c5a37f6496dc34fd280654d27eea0801128205c0b571b694c5a37fccc40f0d8313efb3cb5584704e041fc3caf5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Tue, 25 Sep 2012 20:37:11 GMT" + }, + { + "date": "Fri, 02 Nov 2012 20:14:54 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 20:14:54 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "2993" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "62610" + }, + { + "cache-control": "public, max-age=86400" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 282, + "wire": "88dc6496dc34fd280654d27eea0801128166e32f5c1054c5a37f54012a5f87497ca589d34d1f0f0d85081979c07f6196dc34fd280654d27eea0801128166e32f5c1054c5a37fe4408af2b10649cab0c8931eaf044d4953534088f2b10649cab0e62f0130589aaec3771a4bf4a523f2b0e62c00fa529b5095ac2f71d0690692fff07b05582d43444e", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 03 Nov 2012 13:38:21 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "text/html" + }, + { + "content-length": "103860" + }, + { + "date": "Sat, 03 Nov 2012 13:38:21 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-action": "MISS" + }, + { + "x-cache-age": "0" + }, + { + "cache-control": "private, max-age=0, must-revalidate" + }, + { + "x-lb-nocache": "true" + }, + { + "vary": "X-CDN" + } + ] + }, + { + "seqno": 283, + "wire": "88d2fd5f87352398ac5754df6c96d07abe941094d444a820044a05eb8db371a1298b46ff6196c361be940094d27eea080112820dc6dfb8c894c5a37f6496dc34fd280654d27eea080112820dc6dfb8c894c5a37fd9d10f0d836dc0bfd855846dc65917d0", + "headers": [ + { + ":status": "200" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Mon, 22 Oct 2012 18:53:42 GMT" + }, + { + "date": "Fri, 02 Nov 2012 21:59:32 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 21:59:32 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "5619" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "56332" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 284, + "wire": "88c26c96d07abe941094d444a820044a05eb8db5719794c5a37f6196c361be940094d27eea080112820dc6dfb8cb2a62d1bf6496dc34fd280654d27eea080112820dc6dfb8cb2a62d1bfddd50f0d836d975fdc55846dc6590fd4db5a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Mon, 22 Oct 2012 18:54:38 GMT" + }, + { + "date": "Fri, 02 Nov 2012 21:59:33 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 21:59:33 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "5379" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "56331" + }, + { + "cache-control": "public, max-age=86400" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 285, + "wire": "488264027686c58703025c1f5f92497ca589d34d1f6a1271d882a60e1bf0acf70f0d0130c1e00f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c082ebe17da602b07c85798d2f5886a8eb10649cbfe6", + "headers": [ + { + ":status": "302" + }, + { + "server": "GFE/2.0" + }, + { + "content-type": "text/html; charset=UTF-8" + }, + { + "content-length": "0" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "location": "http://s0.2mdn.net/viewad/2179194/1-1x1.gif" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 286, + "wire": "c1e7e1e6e5bebfe3c00f0d0130e2c20f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c082ebe17da602b07c85798d2f", + "headers": [ + { + ":status": "302" + }, + { + "p3p": "policyref=\"http://googleads.g.doubleclick.net/pagead/gcn_p3p_.xml\", CP=\"CURa ADMa DEVa TAIo PSAo PSDo OUR IND UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "no-cache" + }, + { + "content-type": "text/html; charset=UTF-8" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "GFE/2.0" + }, + { + "content-length": "0" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "content-encoding": "gzip" + }, + { + "location": "http://s0.2mdn.net/viewad/2179194/1-1x1.gif" + } + ] + }, + { + "seqno": 287, + "wire": "88e0c2d86c96c361be94036a693f7504008140bd7021b8cb4a62d1bf6196dc34fd280654d27eea080112807ae05ab806d4c5a37f6496dd6d5f4a01a5349fba820044a01eb816ae01b53168dfe6de0f0d023433e555840bed36ffdd", + "headers": [ + { + ":status": "200" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Fri, 05 Nov 2010 18:11:34 GMT" + }, + { + "date": "Sat, 03 Nov 2012 08:14:05 GMT" + }, + { + "expires": "Sun, 04 Nov 2012 08:14:05 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "43" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "19459" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 288, + "wire": "88768dd06258741e54ad9326e61c5c1f7f2da1bdae0fe74eac8a5fddad4bdab6a9af7427535eebe753570daa5de1b94d5bef7f3fd84089f2b567f05b0b22d1fa868776b5f4e0df408cf2b0d15d454addcb620c7abf8712e05db03a277f5f87497ca58ae819aa0f0d8365b13d5891aec3771a4bf4a523f2b0e62c0d81c71f6b6196dc34fd280654d27eea0801128166e32f5c132a62d1bf408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "CP=\"NOI DSP COR PSAo PSDo OUR BUS OTC\"" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "content-type": "text/plain" + }, + { + "content-length": "3528" + }, + { + "cache-control": "private, max-age=506694" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 289, + "wire": "88f35f96497ca58e83ee3412c3569fb50938ec4153070df8567bf059871a52324f496a4fd0ef768320e52f5885aec3771a4b0f0d03383832f2", + "headers": [ + { + ":status": "200" + }, + { + "p3p": "policyref=\"http://googleads.g.doubleclick.net/pagead/gcn_p3p_.xml\", CP=\"CURa ADMa DEVa TAIo PSAo PSDo OUR IND UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "text/javascript; charset=UTF-8" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-disposition": "attachment" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "server": "cafe" + }, + { + "cache-control": "private" + }, + { + "content-length": "882" + }, + { + "x-xss-protection": "1; mode=block" + } + ] + }, + { + "seqno": 290, + "wire": "88768586b19272fff1f0d36c96c361be940094d27eea0801128076e322b81714c5a37f588ca47e561cc581c13ee32eb20f6497dd6d5f4a004a693f750400b4a01db8cb371b654c5a37ffc70f0d83702117c64084f2b563938d1f739a4523b0fe105b148ed9bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 07:32:16 GMT" + }, + { + "cache-control": "max-age=62963730" + }, + { + "expires": "Sun, 02 Nov 2014 07:33:53 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "content-length": "6112" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 291, + "wire": "88c26c96c361be940094d27eea0801128015c6d9b800298b46fff6d80f0d8371f035f5588ca47e561cc581c13ed3a06dff6496dd6d5f4a004a693f750400b4a00571b72e004a62d1bfcbcac1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Fri, 02 Nov 2012 02:53:00 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "6904" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=62947059" + }, + { + "expires": "Sun, 02 Nov 2014 02:56:02 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 292, + "wire": "88c5f8f7c1da6c96d07abe9413ea6a225410022504cdc6dab8cbca62d1bf0f0d8371a71d588ca47e561cc581c138ebacb8ff6496df3dbf4a320535112a0801694002e003702253168dffcecd", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Mon, 29 Oct 2012 23:54:38 GMT" + }, + { + "content-length": "6467" + }, + { + "cache-control": "max-age=62677369" + }, + { + "expires": "Thu, 30 Oct 2014 00:01:12 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 293, + "wire": "88c8fbfac4dd6c96c361be940094d27eea080112800dc6dcb8d34a62d1bf0f0d836dd7c1588ca47e561cc581c13ed32fb2ef6496dd6d5f4a004a693f750400b4a005700d5c0014c5a37fd1d0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 01:56:44 GMT" + }, + { + "content-length": "5790" + }, + { + "cache-control": "max-age=62943937" + }, + { + "expires": "Sun, 02 Nov 2014 02:04:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 294, + "wire": "88cbfd6c96df3dbf4a05b52f948a08010a810dc68571b7d4c5a37f0f0d840b2171cf588ca47e561cc581c0321136eb3f6496df3dbf4a004a6a22541002d2816ee01db8db8a62d1bfd4d3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Thu, 15 Dec 2011 11:42:59 GMT" + }, + { + "content-length": "13166" + }, + { + "cache-control": "max-age=60312573" + }, + { + "expires": "Thu, 02 Oct 2014 15:07:56 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 295, + "wire": "88ceff02ff01e36c96c361be940094d27eea080112817ee34edc0054c5a37f0f0d8371d0b7588ca47e561cc581c640075d7daf6496dd6d5f4a004a693f750400b4a05fb8d3d702ea98b46fd7d6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 19:47:01 GMT" + }, + { + "content-length": "6715" + }, + { + "cache-control": "max-age=63007794" + }, + { + "expires": "Sun, 02 Nov 2014 19:48:17 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 296, + "wire": "88588da8eb10649cbf4a54759093d85f4085aec1cd48ff86a8eb10649cbf0f0d023432fe6496dd6d5f4a01b5b2db52c2001b5042b8005c0014c5a37f0f28d6b450008191b75d12ce8dd6d669f0b2b3e565a59f0c61291970ae3ae33b3b82307da85f359ac2a20c361be940056c258d61002ca807ee32f5c134a62d1bfed490f48cd540ba0b67735532c8f485c87a7ed4ac699e063ff9de7f2096bdae0fe74eac8a5fc1c46a6ae1b54bbc3729c34e4fe76196dc34fd280654d27eea0801128166e32f5c134a62d1bf7f1c842507417f", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "42" + }, + { + "content-type": "image/gif" + }, + { + "expires": "Sun, 05-Jun-2005 22:00:00 GMT" + }, + { + "set-cookie": "u2=0c1d5772-7a75-4913-9e34-91b1ec36e6763Qv0b0; expires=Fri, 01-Feb-2013 09:38:24 GMT; domain=.serving-sys.com; path=/" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"NOI DEVa OUR BUS UNI\"" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "connection": "close" + } + ] + }, + { + "seqno": 297, + "wire": "887689bf7b3e65a193777b3f5f911d75d0620d263d4c795ba0fb8d04b0d5a70f0d83132e83eec10f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c082ebe17da602b07c85798d2feac4", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "2370" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "location": "http://s0.2mdn.net/viewad/2179194/1-1x1.gif" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 298, + "wire": "88d96496d07abe94032a693f750400b4a059b8cbd704d298b46fff005f88352398ac74acb37f0f0d840bafb80fc37f0388cc52d6b4341bb97ffefd588ca47e561cc581c640e880007f4089f2b511ad51c8324e5f834d9697fd6c96d07abe9403ca693f7504008140b571b66e36e298b46f4088ea52d6b0e83772ff8d49a929ed4c0dfd2948fcc0e859", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Nov 2014 13:38:24 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "17960" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "connection": "Keep-Alive" + }, + { + "x-cache-action": "MISS" + }, + { + "x-cache-age": "0" + }, + { + "cache-control": "max-age=63072000" + }, + { + "x-lb-nocache": "true" + }, + { + "vary": "X-CDN" + }, + { + "last-modified": "Mon, 08 Nov 2010 14:53:56 GMT" + }, + { + "keep-alive": "timeout=5, max=713" + } + ] + }, + { + "seqno": 299, + "wire": "887b8b84842d695b05443c86aa6ff65f87352398ac4c697f6c96df3dbf4a09a5340fd2820044a08171b15c038a62d1bf6196c361be940094d27eea0801128205c69bb8c814c5a37f6496dc34fd280654d27eea0801128205c69bb8c814c5a37f4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb768344b2970f0d840bccbaef408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f5584700ebad75890aed8e8313e94a47e561cc581e71a003f", + "headers": [ + { + ":status": "200" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 24 May 2012 20:52:06 GMT" + }, + { + "date": "Fri, 02 Nov 2012 20:45:30 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 20:45:30 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "18377" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "60774" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 300, + "wire": "880f28fbb1288a1861860d19edbb336ffe78a18c1835070f0db7616490d1353523fdfecfe66c3fa2fc904f56d697bfdf6199fed8ffdf7fcc09cbd7439a5987de6bdb505ba7a70faf6e318c14fda97cf48cd540bd85ee82197a8a9fb53079acd615106eb6afa500cada4fdd61002ca8166e32f5c134a62d1bfed4d634cf031f4088f2b5761c8b48348f89ae46568e61a00fac1f7f15e9acf4189eac2cb07f33a535dc618e885ec2f7410cbd454b1e19231620d5b35afe69a3f9fa52f6b83f9d3ab4a9af742a6bdd7d4c9c6153271bea6adfad4dd0e853269bea70d3914d7c36a97b568534c3c54c9a77a97f06852f69dea6edf0a9af6e05356fbca63c10ff3f7603525349fed86496df3dbf4a002a651d4a05f740a0017000b800298b46ff5f9a1d75d0620d263d4c741f71a0961ab4fd9271d882a60e1bf0acf7798624f6d5d4b27f5a839bd9abced9", + "headers": [ + { + ":status": "200" + }, + { + "set-cookie": "rts_AAAA=MLuBg59Xwl/EEO1FURBA3cAlgmns+ZhxgFZ2Xd28p4N8+qai9qH+vXEtJkM6N3AzKCRseBomFyz6/H0m; Domain=.revsci.net; Expires=Sun, 03-Nov-2013 13:38:24 GMT; Path=/" + }, + { + "x-proc-data": "pd3-bgas09-1" + }, + { + "p3p": "policyref=\"http://js.revsci.net/w3c/rsip3p.xml\", CP=\"NON PSA PSD IVA IVD OTP SAM IND UNI PUR COM NAV INT DEM CNT STA PRE OTC HEA\"" + }, + { + "server": "RSI" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "content-type": "application/javascript;charset=UTF-8" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + } + ] + }, + { + "seqno": 301, + "wire": "88f7f16c96d07abe9403ca693f7504008140b571b66e34f298b46f0f0d840bae041fd36496d07abe94032a693f750400b4a059b8cbd704ca98b46f7b05582d43444e54012a7f148e49a929ed4c0dfd2948fcc0ebecffd8d9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Mon, 08 Nov 2010 14:53:48 GMT" + }, + { + "content-length": "17610" + }, + { + "cache-control": "max-age=63072000" + }, + { + "expires": "Mon, 03 Nov 2014 13:38:23 GMT" + }, + { + "vary": "X-CDN" + }, + { + "access-control-allow-origin": "*" + }, + { + "keep-alive": "timeout=5, max=793" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "image/jpeg" + } + ] + }, + { + "seqno": 302, + "wire": "88de768dd54d464db6154bf79812e05c1fc00f28da445dcd07feef6effe770ffc13cd42f6103e06c2e02fbd75670000003086f00207af5f6bff77b07ff3ed4c1e6b3585441be7b7e94504a693f750400baa059b8cbd704d298b46ffb52f9e919aa81035e38c8b90f4fda9ac699e0634003782d6386a50b34bb4bbf6496c361be940094d27eea0801128166e32f5c134a62d1bf6c96dd6d5f4a01a5349fba820044a059b8cbd704d298b46f58a6a8eb10649cbf4a54759093d85fa5291f9587316007d2951d64d83a9129eca7e94aec3771a4bfe60f1393fe5b03ed8703605830bcd2ce38fe06dcbb7bf97b012a7f0fbbacf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725f535ee854d5c36a9934df52f6ad0a69878a9bb7c3fcff4086f282d9dcb67f85f1e3c38e370f0d0234337f078749a929ed4c016fe1db", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "server": "Omniture DC/2.0.0" + }, + { + "access-control-allow-origin": "*" + }, + { + "set-cookie": "s_vi=[CS]v1|284A8F0905160D8B-600001A1C0108CD4[CE]; Expires=Thu, 2 Nov 2017 13:38:24 GMT; Domain=sa.bbc.com; Path=/" + }, + { + "x-c": "ms-4.4.9" + }, + { + "expires": "Fri, 02 Nov 2012 13:38:24 GMT" + }, + { + "last-modified": "Sun, 04 Nov 2012 13:38:24 GMT" + }, + { + "cache-control": "no-cache, no-store, max-age=0, no-transform, private" + }, + { + "pragma": "no-cache" + }, + { + "etag": "\"50951E50-1A84-669E56BC\"" + }, + { + "vary": "*" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA OUR IND COM NAV STA\"" + }, + { + "xserver": "www665" + }, + { + "content-length": "43" + }, + { + "keep-alive": "timeout=15" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 303, + "wire": "88768586b19272ffdde3cd6c96c361be940094d27eea0801128005c03d700e298b46ff588ca47e561cc581c13ecbacbeff6496dd6d5f4a004a693f750400b4a001702ddc032a62d1bfeb0f0d83742f037f2688ea52d6b0e83772ff4084f2b563938d1f739a4523b0fe105b148ed9bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 00:08:06 GMT" + }, + { + "cache-control": "max-age=62937399" + }, + { + "expires": "Sun, 02 Nov 2014 00:15:03 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "content-length": "7180" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 304, + "wire": "88c36c96e4593e94642a6a225410022502ddc6dab8d054c5a37fe3d30f0d8369f00be9588ca47e561cc581c13cd3c275ef6496c361be94642a6a22541002d28266e09fb8d094c5a37ff0c2c1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Wed, 31 Oct 2012 15:54:41 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "4902" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=62848278" + }, + { + "expires": "Fri, 31 Oct 2014 23:29:42 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 305, + "wire": "88c6e5ebc1d56c96df3dbf4a002a693f75040089413371966e36153168df0f0d8365f641588ca47e561cc581c13ecb6269bf6496dc34fd2800a9a4fdd41002d28266e32fdc03ea62d1bff3c5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 01 Nov 2012 23:33:51 GMT" + }, + { + "content-length": "3930" + }, + { + "cache-control": "max-age=62935245" + }, + { + "expires": "Sat, 01 Nov 2014 23:39:09 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 306, + "wire": "88f35f8b497ca58e83ee3412c3569f0f0d03313733c60f28eaef03a27c212cb215a74b23b24afbed3ef8637a394723607db0942d36f48cbee2ce38cb407997db03ed84ad81e65a89965c73ed42f9acd615106e1a7e94032b693f7584008940b5700f5c134a62d1bfed4ac699e063ed490f48cd540b96594a449bb96928d16d855ea2a75886a8eb10649cbf640130768821e8a0a4498f5041408b20c9395690d614893772ff86a8eb10649cbf408caec1cd48d690d614893772ff86a8eb10649cbffc7f13a7acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725ffe7f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "content-type": "text/javascript" + }, + { + "content-length": "173" + }, + { + "connection": "keep-alive" + }, + { + "set-cookie": "v=72911efde47ed7df994991b8bfad50951e1458d396-6634083950951e50834_3366; expires=Sat, 03-Nov-2012 14:08:24 GMT; path=/; domain=.effectivemeasure.net" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "server": "collection10" + }, + { + "cache-directive": "no-cache" + }, + { + "pragma-directive": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID\"" + } + ] + }, + { + "seqno": 307, + "wire": "88d0f56c96e4593e94642a6a225410022504cdc00ae36ca98b46ff0f0d8365b79f588ca47e561cc581c13cd38fbacf6496c361be94642a6a22541002d28266e01db8dbaa62d1bffdcf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Wed, 31 Oct 2012 23:02:53 GMT" + }, + { + "content-length": "3589" + }, + { + "cache-control": "max-age=62846973" + }, + { + "expires": "Fri, 31 Oct 2014 23:07:57 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 308, + "wire": "88768dd06258741e54ad9326e61c5c1f7f03a1bdae0fe74eac8a5fddad4bdab6a9af7427535eebe753570daa5de1b94d5bef7f3fe04089f2b567f05b0b22d1fa868776b5f4e0df408cf2b0d15d454addcb620c7abf8712e05db03a277f5f87497ca58ae819aa0f0d840b6dbce75891aec3771a4bf4a523f2b0e62c0d81c71d6f6196dc34fd280654d27eea0801128166e32f5c132a62d1bfd6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "CP=\"NOI DSP COR PSAo PSDo OUR BUS OTC\"" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "content-type": "text/plain" + }, + { + "content-length": "15586" + }, + { + "cache-control": "private, max-age=506675" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 309, + "wire": "880f0d841002d87ff86c96df3dbf4a09b535112a0801128176e059b8d34a62d1bf52848fd24a8f0f1390fe40db4d3417246a311240dc8db73f9fc6c5c4c0d8", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "20151" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 25 Oct 2012 17:13:44 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"0544416d4b2cd1:b56\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "CP=\"NOI DSP COR PSAo PSDo OUR BUS OTC\"" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 310, + "wire": "88fafbeb6496d07abe940054ca3a940bef814002e001700053168dff6196dc34fd280654d27eea0801128166e32f5c134a62d1bf0f0d023433da58b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bf4085aec1cd48ff86a8eb10649cbf", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "content-length": "43" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 311, + "wire": "88e07b8b84842d695b05443c86aa6f5f88352398ac74acb37ff16c96d07abe94036a681d8a0801128115c102e32ea98b46ff588ca47e561cc581a101d10596ff6496e4593e94036a681d8a080169408ae081719754c5a37f6196dc34fd280654d27eea0801128166e32f5c1094c5a37f0f0d846df7db7be2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Mon, 05 Mar 2012 12:20:37 GMT" + }, + { + "cache-control": "max-age=42072135" + }, + { + "expires": "Wed, 05 Mar 2014 12:20:37 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:22 GMT" + }, + { + "content-length": "59958" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 312, + "wire": "88e6c35f87352398ac5754dff66c96df697e94640a6a225410022500f5c13971a794c5a37f588ca47e561cc581c13a075d083f6496df3dbf4a320535112a080169403d704e5c6da53168dfca0f0d03313839e6e5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/png" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 08:26:48 GMT" + }, + { + "cache-control": "max-age=62707710" + }, + { + "expires": "Thu, 30 Oct 2014 08:26:54 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "content-length": "189" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 313, + "wire": "88ea6c96c361be94138a6a225410022502ddc0bd71a0a98b46ffc8fa0f0d836da743c7588ca47e561cc581c132f3a0645f6496dd6d5f4a09c535112a08016940b77042b81714c5a37fcde9e8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Fri, 26 Oct 2012 15:18:41 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "5471" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=62387032" + }, + { + "expires": "Sun, 26 Oct 2014 15:22:16 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-pad": "avoid browser bug" + } + ] + }, + { + "seqno": 314, + "wire": "88edcac9e8fc6c96df697e940b6a693f75040085403f71a7ee32ca98b46f0f0d8365c69b588ca47e561cc581b7c4eb6d89bf6496dc34fd282029b8b5a82005a502ddc03371a7d4c5a37fd0ec", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 15 Nov 2011 09:49:33 GMT" + }, + { + "content-length": "3645" + }, + { + "cache-control": "max-age=59275525" + }, + { + "expires": "Sat, 20 Sep 2014 15:03:49 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 315, + "wire": "88f06c96c361be940094d27eea080112800dc641704fa98b46ffecce5a839bd9ab0f0d836df65dce588ca47e561cc581c13ed08400ff6496dd6d5f4a004a693f750400b4a0037196ee01a53168dfd8f0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Fri, 02 Nov 2012 01:30:29 GMT" + }, + { + "x-pad": "avoid browser bug" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "5937" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "max-age=62942201" + }, + { + "expires": "Sun, 02 Nov 2014 01:35:04 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 316, + "wire": "88f4d1d0c06c96c361be940094d27eea0801128176e342b81694c5a37f588ca47e561cc581c6400009f77f6496dd6d5f4a004a693f750400b4a05db8d33704053168dfdb0f0d8371e03bf3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 17:42:14 GMT" + }, + { + "cache-control": "max-age=63000297" + }, + { + "expires": "Sun, 02 Nov 2014 17:43:20 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "content-length": "6807" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 317, + "wire": "88f7d4d3f2c36c96e4593e94642a6a225410022504cdc03371a0298b46ff0f0d83699785588ca47e561cc581c13cd38fb4e76496c361be94642a6a22541002d28266e01db8c814c5a37fdaf6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "x-pad": "avoid browser bug" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Wed, 31 Oct 2012 23:03:40 GMT" + }, + { + "content-length": "4382" + }, + { + "cache-control": "max-age=62846946" + }, + { + "expires": "Fri, 31 Oct 2014 23:07:30 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 318, + "wire": "88fad7d6c66c96df697e94640a6a225410022504cdc69fb8dbea62d1bf0f0d836c4fbf588ca47e561cc581c13ae3c271cf6496c361be94642a6a22541002d2800dc0b9702053168dffddf9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 30 Oct 2012 23:49:59 GMT" + }, + { + "content-length": "5299" + }, + { + "cache-control": "max-age=62768266" + }, + { + "expires": "Fri, 31 Oct 2014 01:16:10 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:24 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 319, + "wire": "887f3a842507417f6196dc34fd280654d27eea0801128166e32f5c138a62d1bfe9e8e7e60f0d01315885aec3771a4b5f92497ca58ae819aafb50938ec415305a99567b", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "date": "Sat, 03 Nov 2012 13:38:26 GMT" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "CP=\"NOI DSP COR PSAo PSDo OUR BUS OTC\"" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "content-length": "1" + }, + { + "cache-control": "private" + }, + { + "content-type": "text/plain; charset=utf-8" + } + ] + }, + { + "seqno": 320, + "wire": "88c1c0ebeae9e80f0d0131bfbe", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "date": "Sat, 03 Nov 2012 13:38:26 GMT" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "CP=\"NOI DSP COR PSAo PSDo OUR BUS OTC\"" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "content-length": "1" + }, + { + "cache-control": "private" + }, + { + "content-type": "text/plain; charset=utf-8" + } + ] + }, + { + "seqno": 321, + "wire": "880f0d8413efb8df5f87352398ac4c697f6c96df3dbf4a09b535112a0801128176e045719694c5a37fe50f1390fe40291e8ca49198c449037236dcfe7fedecebe77f0488ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "29965" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 25 Oct 2012 17:12:34 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"02d8becd3b2cd1:b56\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "CP=\"NOI DSP COR PSAo PSDo OUR BUS OTC\"" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "date": "Sat, 03 Nov 2012 13:38:23 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 322, + "wire": "88c46196dc34fd280654d27eea0801128166e32f5c642a62d1bfefeeedec0f0d0131c3c2", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "date": "Sat, 03 Nov 2012 13:38:31 GMT" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "CP=\"NOI DSP COR PSAo PSDo OUR BUS OTC\"" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "content-length": "1" + }, + { + "cache-control": "private" + }, + { + "content-type": "text/plain; charset=utf-8" + } + ] + }, + { + "seqno": 323, + "wire": "88be768586b19272ff6495dc34fd2800a994752820000a0017000b800298b46fe5fa7f31caacf4189eac2cb07f33a535dc618f1e3c2f5164424695c87a58f0c918ad9ad7f34d1fcfd297b5c1fce9d5914bfbb5a97b56d534e4bea6bdd0a90dfd0a6ae1b54c9a6fa9a61e2a5ed5a3f90f0d023433c8c4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:31 GMT" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 01 Jan 2000 00:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.nedstat.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND NAV COM\"" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 324, + "wire": "88c1c06c96e4593e940054d03b141000e2816ee059b8db8a62d1bfeb0f0d0234335896a47e561cc5801f4a547588324e5837152b5e39fa98bf6496dc34fd280654d27eea0801128166e32f5c642a62d1bf4088ea52d6b0e83772ff8d49a929ed4c0107d2948fcc0f877f0788cc52d6b4341bb97fc9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:31 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Wed, 01 Mar 2006 15:13:56 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "43" + }, + { + "cache-control": "max-age=0, no-cache=Set-Cookie" + }, + { + "expires": "Sat, 03 Nov 2012 13:38:31 GMT" + }, + { + "keep-alive": "timeout=10, max=91" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 325, + "wire": "887689bf7b3e65a193777b3f5f911d75d0620d263d4c795ba0fb8d04b0d5a70f0d826422db6196dc34fd280654d27eea0801128166e32f5c644a62d1bf0f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c082ebe17da602b07c85798d2f5886a8eb10649cbfef", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "312" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:32 GMT" + }, + { + "location": "http://s0.2mdn.net/viewad/2179194/1-1x1.gif" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 326, + "wire": "88c1c00f0d830882e7ddbf0f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c082ebe17da602b07c85798d2fbeef", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "1216" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:32 GMT" + }, + { + "location": "http://s0.2mdn.net/viewad/2179194/1-1x1.gif" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 327, + "wire": "88d1bffbfa54012afa5f87497ca589d34d1f0f0d8369c6430f28d0d7b6b241ff31d9cfc63bf881703ff31d9cfbe3bf883705ff3febee43d2335500e442f59cd526c3d142e43d3f6a5634cf031f6a17cd66b0a88341eafa500cada4fdd61002d28166e32f5c644a62d1bfef", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "date": "Sat, 03 Nov 2012 13:38:32 GMT" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "CP=\"NOI DSP COR PSAo PSDo OUR BUS OTC\"" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-type": "text/html" + }, + { + "content-length": "4631" + }, + { + "set-cookie": "PRpc=|HrYwHDG0:1|HrYvHDG1:2|#;domain=ads.pointroll.com; path=/; expires=Mon, 03-Nov-2014 13:38:32 GMT;" + } + ] + }, + { + "seqno": 328, + "wire": "88c3c20f0d826442df6196dc34fd280654d27eea0801128166e32f5c65953168df0f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c082ebe17da602b07c85798d2fc1f2", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "322" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:33 GMT" + }, + { + "location": "http://s0.2mdn.net/viewad/2179194/1-1x1.gif" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 329, + "wire": "88ccf1f0e06c96c361be941014cb6d0a080112810dc6c1704e298b46ff588ca47e561cc581b65f03a1781f6496dd6d5f4a080a65b6850400b4a04371b0dc644a62d1bfc50f0d83105a7fd1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 20 Jul 2012 11:50:26 GMT" + }, + { + "cache-control": "max-age=53907180" + }, + { + "expires": "Sun, 20 Jul 2014 11:51:32 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:32 GMT" + }, + { + "content-length": "2149" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 330, + "wire": "887f0efdacf4189eac2cb07f33a535dc61898e79a828e442f32f21ed8e82928313aaf5152c56398a39189895455b35c4bf9a68fe7e94bdae0fe6f70da3521bfa06a5fc1c46a6f8721d4d7ba13a9af75f3a9ab86d53269bea70d3914d7c36a9934ef52fe0d0a6edf0a9af6e052f6ad0a69878a9ab7de534eac8a5fddad4bdab6ff35f96497ca58e83ee3412c3569fb50938ec4153070df8567b4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb59871a52324f496a4fe7c5768320e52fda0f0d830b8e03408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f", + "headers": [ + { + ":status": "200" + }, + { + "p3p": "policyref=\"http://googleads.g.doubleclick.net/pagead/gcn_p3p_.xml\", CP=\"CURa ADMa DEVa TAIo PSAo PSDo OUR IND UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "text/javascript; charset=UTF-8" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-disposition": "attachment" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:33 GMT" + }, + { + "server": "cafe" + }, + { + "cache-control": "private" + }, + { + "content-length": "1660" + }, + { + "x-xss-protection": "1; mode=block" + } + ] + }, + { + "seqno": 331, + "wire": "88cdcc0f0d03363039e9c70f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c082ebe17da602b07c85798d2fca4085aec1cd48ff86a8eb10649cbf", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "609" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:33 GMT" + }, + { + "location": "http://s0.2mdn.net/viewad/2179194/1-1x1.gif" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 332, + "wire": "885886a8eb2127b0bfcaeb6401307b8b84842d695b05443c86aa6f4086f2b5281c86938713e164017c2d7fd0e20f0d8313cf0b", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "text/html" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "0" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-msadid": "291301914" + }, + { + "date": "Sat, 03 Nov 2012 13:38:32 GMT" + }, + { + "connection": "close" + }, + { + "content-length": "2882" + } + ] + }, + { + "seqno": 333, + "wire": "887684aa6355e7cddf798624f6d5d4b27fde7f178749a929ed4c02076496df3dbf4a002a5f29140befb4a05cb8005c0014c5a37fc6d37f0dd2acf4189eac2cb07f33a535dc618f1e3c2e6a6cf07b2893c1a42ae43d2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725f535ee85486fe853570daa64d37d4e1a7229a61e2a5ed5a3f9f", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx" + }, + { + "date": "Sat, 03 Nov 2012 13:38:33 GMT" + }, + { + "content-type": "image/gif" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "keep-alive": "timeout=20" + }, + { + "expires": "Thu, 01 Dec 1994 16:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.imrworldwide.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND UNI NAV COM\"" + } + ] + }, + { + "seqno": 334, + "wire": "88d7d60f0d826440f3d10f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c082ebe17da602b07c85798d2fd4c7", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "320" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:33 GMT" + }, + { + "location": "http://s0.2mdn.net/viewad/2179194/1-1x1.gif" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 335, + "wire": "88d60f0d02353056034745546496df697e94038a693f750400894082e099b8d3ca62d1bfd3e3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "50" + }, + { + "allow": "GET" + }, + { + "expires": "Tue, 06 Nov 2012 10:23:48 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:33 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 336, + "wire": "488264027689aa6355e5802ef2edb5ea5f8b1d75d0620d263d4c79a68fe6d80f28bbacde4b4452b6271c71c03d007ed4be7a466aa05e43525424f615721e9fb53079acd615106eb6afa500cada4fdd61002ca8166e32f5c138a62d1bff0f1f9b9d29aee30c10f524b525790d495093d855c87a58acde4b42f31a5f0f0d0130", + "headers": [ + { + ":status": "302" + }, + { + "server": "nginx/0.8.54" + }, + { + "date": "Sat, 03 Nov 2012 13:38:26 GMT" + }, + { + "content-type": "application/xml" + }, + { + "connection": "keep-alive" + }, + { + "access-control-allow-origin": "*" + }, + { + "set-cookie": "pixel_f52666608=1; Domain=.dimestore.com; Expires=Sun, 03-Nov-2013 13:38:26 GMT" + }, + { + "location": "http://content.dimestore.com/pixel.gif" + }, + { + "content-length": "0" + } + ] + }, + { + "seqno": 337, + "wire": "88e46496dc34fd280654d27eea0801128166e32f5c640a62d1bfd9d80f0d847c2e841f6196dc34fd280654d27eea0801128166e32f5c640a62d1bfe8408af2b10649cab0c8931eaf044d4953534088f2b10649cab0e62f0130589aaec3771a4bf4a523f2b0e62c00fa529b5095ac2f71d0690692ff4089f2b511ad51c8324e5f834d96977b05582d43444e", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 03 Nov 2012 13:38:30 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "text/html" + }, + { + "content-length": "91710" + }, + { + "date": "Sat, 03 Nov 2012 13:38:30 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-cache-action": "MISS" + }, + { + "x-cache-age": "0" + }, + { + "cache-control": "private, max-age=0, must-revalidate" + }, + { + "x-lb-nocache": "true" + }, + { + "vary": "X-CDN" + } + ] + }, + { + "seqno": 338, + "wire": "88e3e20f0d033533385a839bd9abde", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "538" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:33 GMT" + } + ] + }, + { + "seqno": 339, + "wire": "88e4e30f0d03333536bede0f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c082ebe17da602b07c85798d2fe1d4", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "356" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:33 GMT" + }, + { + "location": "http://s0.2mdn.net/viewad/2179194/1-1x1.gif" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 340, + "wire": "88e4e30f0d03353339bede", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "539" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:33 GMT" + } + ] + }, + { + "seqno": 341, + "wire": "88daded46496c361be940054ca3a940bef814002e001700053168dffe2e4d9e50f0d03353435d6bf0f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c082ebe17da602b07c85798d2f", + "headers": [ + { + ":status": "200" + }, + { + "p3p": "policyref=\"http://googleads.g.doubleclick.net/pagead/gcn_p3p_.xml\", CP=\"CURa ADMa DEVa TAIo PSAo PSDo OUR IND UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "date": "Sat, 03 Nov 2012 13:38:33 GMT" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "no-cache" + }, + { + "content-type": "application/x-javascript" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-length": "545" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "content-encoding": "gzip" + }, + { + "location": "http://s0.2mdn.net/viewad/2179194/1-1x1.gif" + } + ] + }, + { + "seqno": 342, + "wire": "88f56196dc34fd280654d27eea0801128166e32f5c65a53168df768dd06258741e54ad9326e61c5c1f7f0fa1bdae0fe74eac8a5fddad4bdab6a9af7427535eebe753570daa5de1b94d5bef7f3f4089f2b567f05b0b22d1fa868776b5f4e0df408cf2b0d15d454addcb620c7abf8712e05db03a277f0f0d0131f8f7", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "date": "Sat, 03 Nov 2012 13:38:34 GMT" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "CP=\"NOI DSP COR PSAo PSDo OUR BUS OTC\"" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "content-length": "1" + }, + { + "cache-control": "private" + }, + { + "content-type": "text/plain; charset=utf-8" + } + ] + }, + { + "seqno": 343, + "wire": "ce7686c58703025c1f5f92497ca589d34d1f6a1271d882a60e1bf0acf70f0d0130c6e60f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c082ebe17da602b07c85798d2fe9dc", + "headers": [ + { + ":status": "302" + }, + { + "server": "GFE/2.0" + }, + { + "content-type": "text/html; charset=UTF-8" + }, + { + "content-length": "0" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:33 GMT" + }, + { + "location": "http://s0.2mdn.net/viewad/2179194/1-1x1.gif" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 344, + "wire": "88eceb0f0d826441c6c4", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "321" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:34 GMT" + } + ] + }, + { + "seqno": 345, + "wire": "d0e2e6dcc5e9bee0bf0f0d0130ddc60f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c082ebe17da602b07c85798d2f", + "headers": [ + { + ":status": "302" + }, + { + "p3p": "policyref=\"http://googleads.g.doubleclick.net/pagead/gcn_p3p_.xml\", CP=\"CURa ADMa DEVa TAIo PSAo PSDo OUR IND UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "date": "Sat, 03 Nov 2012 13:38:33 GMT" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "no-cache" + }, + { + "content-type": "text/html; charset=UTF-8" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "GFE/2.0" + }, + { + "content-length": "0" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "content-encoding": "gzip" + }, + { + "location": "http://s0.2mdn.net/viewad/2179194/1-1x1.gif" + } + ] + }, + { + "seqno": 346, + "wire": "885f88352398ac74acb37f0f0d840bce001fd36496dd6d5f4a01a5349fba820044a01db8066e002a62d1bfc6f8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "18600" + }, + { + "allow": "GET" + }, + { + "expires": "Sun, 04 Nov 2012 07:03:01 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:34 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 347, + "wire": "88588da8eb10649cbf4a54759093d85fdf0f0d023432fb6496dd6d5f4a01b5b2db52c2001b5042b8005c0014c5a37f0f28d6b450008191b75d12ce8dd6d669f0b2b3e565a59f0c61291970ae3ae33b3b8239bed42f9acd6151061b0df4a002b612c6b080165403f7197ae32d298b46ffb5243d2335502e82d9dcd54cb23d21721e9fb52b1a67818fecc57f0796bdae0fe74eac8a5fc1c46a6ae1b54bbc3729c34e4fe7eb7f33842507417f", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "42" + }, + { + "content-type": "image/gif" + }, + { + "expires": "Sun, 05-Jun-2005 22:00:00 GMT" + }, + { + "set-cookie": "u2=0c1d5772-7a75-4913-9e34-91b1ec36e6763Qv0bg; expires=Fri, 01-Feb-2013 09:38:34 GMT; domain=.serving-sys.com; path=/" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"NOI DEVa OUR BUS UNI\"" + }, + { + "date": "Sat, 03 Nov 2012 13:38:33 GMT" + }, + { + "connection": "close" + } + ] + }, + { + "seqno": 348, + "wire": "88f2f10f0d03323435ccca0f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c082ebe17da602b07c85798d2fefe2", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "245" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:34 GMT" + }, + { + "location": "http://s0.2mdn.net/viewad/2179194/1-1x1.gif" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 349, + "wire": "887689aa6355e5802ee2ecb75f87352398ac4c697f0f0d0234396c96c361be94036a436cca08010a817ae34ddc644a62d1bff152848fd24a8fce7f0388ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx/0.6.35" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "49" + }, + { + "last-modified": "Fri, 05 Aug 2011 18:45:32 GMT" + }, + { + "access-control-allow-origin": "*" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 13:38:34 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 350, + "wire": "88c1e4d16496d07abe940054ca3a940bef814002e001700053168dff6196dc34fd280654d27eea0801128166e32f5c65b53168df0f0d023433c058b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfea", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:35 GMT" + }, + { + "content-length": "43" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 351, + "wire": "88768586b19272ff6c96e4593e940bea6e2d6a0801128266e320b8cbea62d1bf0f1394fe632c816e35a4001668830b8e352bee36407f3fc40f0d8365913d7f0ab5acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a97f0711a9be1c8353570daa5de1b94e1a727f3fcc2c4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Wed, 19 Sep 2012 23:30:39 GMT" + }, + { + "etag": "\"bed15b-d00-4ca1664f965c0\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "3328" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR DEVa TAIa OUR BUS UNI\"" + }, + { + "content-type": "application/x-javascript" + }, + { + "date": "Sat, 03 Nov 2012 13:38:35 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 352, + "wire": "88e86196dc34fd280654d27eea0801128166e32f5c65d53168dffdca0f289fbba3f22675de803f6a5634cf031f6a487a466aa05fb9cc42ca6ee55c87a7ef7f00c4acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5ed5b54d392fa97b86d52fe0e2a6f87229af742a64e30a9ab86d5376f854e1a7229a61e2a64d3bff958a1a47e561cc5801f4a547588324e5fa52a3ac849ec2fd294da84ad617b8e83483497f064022d317b93e082d8b43316a4fd424216b4ad82a21e435537dc0f0d83784d03", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx" + }, + { + "date": "Sat, 03 Nov 2012 13:38:37 GMT" + }, + { + "content-type": "application/x-javascript" + }, + { + "connection": "close" + }, + { + "set-cookie": "BMX_3PC=1; path=/; domain=.voicefive.com;" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI COR NID CUR DEV TAI PSA IVA OUR STA UNI NAV INT\"" + }, + { + "cache-control": "max-age=0, no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "expires": "-1" + }, + { + "vary": "User-Agent,Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "8240" + } + ] + }, + { + "seqno": 353, + "wire": "88c5c35f8b497ca58e83ee3412c3569f6496dc34fd280654d27eea0801128166e32f5c65d53168df5895a47e561cc5801f4a547588324e5fa52a3ac849ec2ff5c50f0d03333932cc0f28bdbda2fdf821872722ecc1f3f721e919aa808340e82d2590c35c87a7eeb1a67818fb2f9acd615106eb6afa500d29a4fdd410022502cdc65eb8cbaa62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR DEVa TAIa OUR BUS UNI\"" + }, + { + "content-type": "text/javascript" + }, + { + "expires": "Sat, 03 Nov 2012 13:38:37 GMT" + }, + { + "cache-control": "max-age=0, no-cache, no-store" + }, + { + "pragma": "no-cache" + }, + { + "date": "Sat, 03 Nov 2012 13:38:37 GMT" + }, + { + "content-length": "392" + }, + { + "connection": "keep-alive" + }, + { + "set-cookie": "CMDD=AAIWeQE*;domain=casalemedia.com;path=/;expires=Sun, 04 Nov 2012 13:38:37 GMT" + } + ] + }, + { + "seqno": 354, + "wire": "880f28ff09b1288a1861860d19edbb336ffe78a18c1835070f0db7616490d1353523fdc70f9ca8f5bf38a7f0c72bef54f8bf793191acbe098ddc0d43a7f30decd37e51e51d387be62c2ec92e8f3a441a78654ffb6ff7ccfe2083ed4be7a466aa05ec2f7410cbd454fda983cd66b0a88375b57d280656d27eeb08016540b37197ae32ea98b46ffb5358d33c0c7f4088f2b5761c8b48348f89ae46568e61a002583f7f06e9acf4189eac2cb07f33a535dc618e885ec2f7410cbd454b1e19231620d5b35afe69a3f9fa52f6b83f9d3ab4a9af742a6bdd7d4c9c6153271bea6adfad4dd0e853269bea70d3914d7c36a97b568534c3c54c9a77a97f06852f69dea6edf0a9af6e05356fbca63c10ff3f76035253495886a8eb10649cbff96496df3dbf4a002a651d4a05f740a0017000b800298b46ff5f9a1d75d0620d263d4c741f71a0961ab4fd9271d882a60e1bf0acf7f5e5f8cb", + "headers": [ + { + ":status": "200" + }, + { + "set-cookie": "rts_AAAA=MLuBg59Xwl/EEO1FURBA3cAlgmns+bAxJsyTL2hw/WD8n92ZW/I4JwcH7E4ANXFCKgXlxsjUzY2F7dfMxN21mUJt+5Zxhw==; Domain=.revsci.net; Expires=Sun, 03-Nov-2013 13:38:37 GMT; Path=/" + }, + { + "x-proc-data": "pd3-bgas02-1" + }, + { + "p3p": "policyref=\"http://js.revsci.net/w3c/rsip3p.xml\", CP=\"NON PSA PSD IVA IVD OTP SAM IND UNI PUR COM NAV INT DEM CNT STA PRE OTC HEA\"" + }, + { + "server": "RSI" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "content-type": "application/javascript;charset=UTF-8" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "date": "Sat, 03 Nov 2012 13:38:37 GMT" + } + ] + }, + { + "seqno": 355, + "wire": "88cb768dd54d464db6154bf79812e05c1f54012a0f28da445dcd07feef6effe770ffc13cd42f6103e06c2e02fbd75670000003086f00207af5f6bff77b07ff3ed4c1e6b3585441be7b7e94504a693f750400baa059b8cbd719754c5a37fda97cf48cd54081af1c645c87a7ed4d634cf0314003782d6386a50b34bb4bbf6496c361be940094d27eea0801128166e32f5c65d53168df6c97dd6d5f4a01a5349fba820044a059b8cbd719754c5a37ff58a6a8eb10649cbf4a54759093d85fa5291f9587316007d2951d64d83a9129eca7e94aec3771a4bf4085aec1cd48ff86a8eb10649cbf0f1393fe5b03ed870377d6109e79615f770e17db73f97b012a7f0bbbacf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725f535ee854d5c36a9934df52f6ad0a69878a9bb7c3fcff4086f282d9dcb67f85f1e3c32f070f0d0234334088ea52d6b0e83772ff8749a929ed4c016f7f1e88cc52d6b4341bb97fe1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:37 GMT" + }, + { + "server": "Omniture DC/2.0.0" + }, + { + "access-control-allow-origin": "*" + }, + { + "set-cookie": "s_vi=[CS]v1|284A8F0905160D8B-600001A1C0108CD4[CE]; Expires=Thu, 2 Nov 2017 13:38:37 GMT; Domain=sa.bbc.com; Path=/" + }, + { + "x-c": "ms-4.4.9" + }, + { + "expires": "Fri, 02 Nov 2012 13:38:37 GMT" + }, + { + "last-modified": "Sun, 04 Nov 2012 13:38:37 GMT" + }, + { + "cache-control": "no-cache, no-store, max-age=0, no-transform, private" + }, + { + "pragma": "no-cache" + }, + { + "etag": "\"50951E5D-2288-2D7FF956\"" + }, + { + "vary": "*" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA OUR IND COM NAV STA\"" + }, + { + "xserver": "www381" + }, + { + "content-length": "43" + }, + { + "keep-alive": "timeout=15" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 356, + "wire": "88d7d20f0d03313733de0f28eaef046f8238c0d09e6db95f009b71b23ef8637a394723607db0942d36f48cbee2ce38cb407997db03ed84adc8f32d44cb2e39f6a17cd66b0a88370d3f4a0195b49fbac20044a05ab807ae32ea98b46ffb52b1a67818fb5243d2335502e596529126ee5a4a345b6157a8a9cc640130768821e8a0a4498f5041408b20c9395690d614893772ff86a8eb10649cbf408caec1cd48d690d614893772ff86a8eb10649cbfc77f06a7acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725ffe7f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:38:37 GMT" + }, + { + "content-type": "text/javascript" + }, + { + "content-length": "173" + }, + { + "connection": "keep-alive" + }, + { + "set-cookie": "v=b90bb042855f902565c991b8bfad50951e1458d396-6634083950951e5d834_3366; expires=Sat, 03-Nov-2012 14:08:37 GMT; path=/; domain=.effectivemeasure.net" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "server": "collection10" + }, + { + "cache-directive": "no-cache" + }, + { + "pragma-directive": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID\"" + } + ] + }, + { + "seqno": 357, + "wire": "89e67b8b84842d695b05443c86aa6ff7e3dd0f0d0130e4e1c9", + "headers": [ + { + ":status": "204" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:38:37 GMT" + }, + { + "content-length": "0" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 358, + "wire": "88769086b19272b025c4b85f53fae151bcff7f5f961d75d0620d263d4c7441eafb24e3b1054c1c37e159efd0409419085421621ea4d87a161d141fc2d495339e447f95d7ab76ffa53160dff4a6be1bfe94d5af7e4d5a777f409419085421621ea4d87a161d141fc2d3947216c47f99bc7a925a92b6ff5597e94fc5b697b5a5424b22dc8c99fe94f90f0d8208455888a47e561cc5802effe2e9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache/2.2.19 (Unix)" + }, + { + "content-type": "application/json;charset=UTF-8" + }, + { + "access-control-allow-origin": "*" + }, + { + "access-control-allow-methods": "POST, GET, PUT, OPTIONS" + }, + { + "access-control-allow-headers": "Content-Type, X-Requested-With, *" + }, + { + "content-length": "112" + }, + { + "cache-control": "max-age=17" + }, + { + "date": "Sat, 03 Nov 2012 13:38:37 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 359, + "wire": "88e56c96c361be94134a65b6a5040036a08571b0dc65953168df0f1393fe652902f9160c6b3328db08da8de23ad03f9febe4f5c45a839bd9ab0f0d023338e4eb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Fri, 24 Jun 2005 22:51:33 GMT" + }, + { + "etag": "\"fec19c-1b-3fa51a4b8c740\"" + }, + { + "accept-ranges": "bytes" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR DEVa TAIa OUR BUS UNI\"" + }, + { + "content-type": "text/html; charset=UTF-8" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "38" + }, + { + "date": "Sat, 03 Nov 2012 13:38:37 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 360, + "wire": "88e76c96d07abe941094d444a820044a05bb8db7719714c5a37f0f1392fe5a005c956786b34420dd289b180a007f3fede6f7c6bf0f0d033133356196dc34fd280654d27eea0801128166e32f5c65e53168dfed", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Mon, 22 Oct 2012 15:55:36 GMT" + }, + { + "etag": "\"4016f-8a-4cca7e25a0e00\"" + }, + { + "accept-ranges": "bytes" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR DEVa TAIa OUR BUS UNI\"" + }, + { + "content-type": "text/html; charset=UTF-8" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "135" + }, + { + "date": "Sat, 03 Nov 2012 13:38:38 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 361, + "wire": "887689bf7b3e65a193777b3f5f911d75d0620d263d4c795ba0fb8d04b0d5a70f0d03323733c2c00f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c082ebe17da602b07c85798d2fddd46496dc34fd280654d27eea0801128166e32f5c65e53168df", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "273" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:38:38 GMT" + }, + { + "location": "http://s0.2mdn.net/viewad/2179194/1-1x1.gif" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 03 Nov 2012 13:38:38 GMT" + } + ] + }, + { + "seqno": 362, + "wire": "88f56196dc34fd280654d27eea0801128166e32f5c682a62d1bf768dd06258741e54ad9326e61c5c1f7f0ea1bdae0fe74eac8a5fddad4bdab6a9af7427535eebe753570daa5de1b94d5bef7f3f4089f2b567f05b0b22d1fa868776b5f4e0df408cf2b0d15d454addcb620c7abf8712e05db03a277f0f0d01315885aec3771a4b5f92497ca58ae819aafb50938ec415305a99567b", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "date": "Sat, 03 Nov 2012 13:38:41 GMT" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "p3p": "CP=\"NOI DSP COR PSAo PSDo OUR BUS OTC\"" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "content-length": "1" + }, + { + "cache-control": "private" + }, + { + "content-type": "text/plain; charset=utf-8" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_24.json b/http/http-client/src/test/resources/hpack-test-case/story_24.json new file mode 100644 index 000000000..5156550b3 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_24.json @@ -0,0 +1,1253 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "488264025f95497ca589d34d1f6a1271d882a60320eb3cf36fac1f408721eaa8a4498f57842507417f0f1f9b9d29aee30c78f1e17258334c8a0c84ae7b2660719ed4b08324a863798624f6d5d4b27f6196dc34fd280654d27eea0801128166e32d5c0b8a62d1bf768586b19272ff", + "headers": [ + { + ":status": "302" + }, + { + "content-type": "text/html; charset=iso-8859-1" + }, + { + "connection": "close" + }, + { + "location": "http://www.craigslist.org/about/sites/" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 13:34:16 GMT" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 1, + "wire": "88c15890aed8e8313e94a47e561cc5802d34007f6c96dc34fd280654d27eea0801128105c03371a6d4c5a37f6196dc34fd280654d27eea0801128105c03371a6d4c5a37f5a839bd9ab7b8b84842d695b05443c86aa6f0f0d84081969afc7408bf2b4b60e92ac7ad263d48f9d868a0fe16c361e95274a6b45c61894f65b4a17258334c8a0c84ae7b26fc46496d07abe94032a5f2914100225020b8066e34da98b46ff", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "public, max-age=14400" + }, + { + "last-modified": "Sat, 03 Nov 2012 10:03:45 GMT" + }, + { + "date": "Sat, 03 Nov 2012 10:03:45 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "10344" + }, + { + "content-type": "text/html; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 10:03:45 GMT" + } + ] + }, + { + "seqno": 2, + "wire": "88c85891a47e561cc5804dbe20001f4a576c74189f6c96df697e940b8a6a2254100225042b8066e000a62d1bff6196df697e940b8a6a2254100225042b8066e000a62d1bffc4c30f0d83081f7f5f94497ca582211f6a1271d882a60320eb3cf36fac1fc3c96496df3dbf4a05b5349fba820044a085700cdc0014c5a37f", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=2592000, public" + }, + { + "last-modified": "Tue, 16 Oct 2012 22:03:00 GMT" + }, + { + "date": "Tue, 16 Oct 2012 22:03:00 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "1099" + }, + { + "content-type": "text/css; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Thu, 15 Nov 2012 22:03:00 GMT" + } + ] + }, + { + "seqno": 3, + "wire": "88cd588ea47e561cc5802dfd295db1d0627f6c96dc34fd280654d27eea0801128166e32cdc6df53168df6196dc34fd280654d27eea0801128166e32cdc6df53168dfc9c80f0d8371969a5f99497ca58e83ee3412c3569fb50938ec41530190759e79b7d60fc8ce6496dc34fd280654d27eea0801128166e32d5c0b4a62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=15, public" + }, + { + "last-modified": "Sat, 03 Nov 2012 13:33:59 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:33:59 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "6344" + }, + { + "content-type": "text/javascript; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 03 Nov 2012 13:34:14 GMT" + } + ] + }, + { + "seqno": 4, + "wire": "88d2c76c96df697e9403ea6a225410022500e5c0b3704f298b46ff6196df697e9403ea6a225410022500e5c0b3704f298b46ffcdcc0f0d8413c00bbfc1cbd16496df3dbf4a01e5349fba820044a01cb8166e09e53168df", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=2592000, public" + }, + { + "last-modified": "Tue, 09 Oct 2012 06:13:28 GMT" + }, + { + "date": "Tue, 09 Oct 2012 06:13:28 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "28017" + }, + { + "content-type": "text/javascript; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Thu, 08 Nov 2012 06:13:28 GMT" + } + ] + }, + { + "seqno": 5, + "wire": "88d56c96d07abe94132a65b6a504003ca099b8072e042a62d1bf5892aed8e8313e94a47e561cc58190b6cb80000152848fd24a8f6196df697e9403ea6a225410022500ddc659b800298b46ffd10f0d83085b075f87497ca58ae819aad76496c361be9403aa6a225410042500ddc659b800298b46ff", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "last-modified": "Mon, 23 Jun 2008 23:06:11 GMT" + }, + { + "cache-control": "public, max-age=315360000" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Tue, 09 Oct 2012 05:33:00 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "1150" + }, + { + "content-type": "text/plain" + }, + { + "server": "Apache" + }, + { + "expires": "Fri, 07 Oct 2022 05:33:00 GMT" + } + ] + }, + { + "seqno": 6, + "wire": "88db588fa47e561cc58197000fa52bb63a0c4f6c96dc34fd280654d27eea0801128115c65cb8db4a62d1bf0f28bf25114859629eb81139c7423ed490f48cd540b92c19a6450642573d937da958d33c0c7da85f359ac2a20dd6d5f4a0195b49fbac165408ae32e5c6da53168dff6196dc34fd280654d27eea0801128115c65cb8db4a62d1bfd7d60f0d83704d37dfd5db6496dc34fd280654d27eea0801128166e32e5c6da53168df", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=3600, public" + }, + { + "last-modified": "Sat, 03 Nov 2012 12:36:54 GMT" + }, + { + "set-cookie": "cl_def_hp=shoals; domain=.craigslist.org; path=/; expires=Sun, 03-Nov-13 12:36:54 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:36:54 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "6245" + }, + { + "content-type": "text/html; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 03 Nov 2012 13:36:54 GMT" + } + ] + }, + { + "seqno": 7, + "wire": "88dfd46c96df3dbf4a002a693f750400894102e36cdc65d53168df6196df3dbf4a002a693f750400894102e36cdc65d53168dfdad90f0d83700d3bd3d8de6496dc34fd2800a97ca450400894102e36cdc65d53168dff", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=2592000, public" + }, + { + "last-modified": "Thu, 01 Nov 2012 20:53:37 GMT" + }, + { + "date": "Thu, 01 Nov 2012 20:53:37 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "6047" + }, + { + "content-type": "text/css; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 01 Dec 2012 20:53:37 GMT" + } + ] + }, + { + "seqno": 8, + "wire": "88e2d76c96df3dbf4a002a693f750400894102e36cdc0baa62d1bf6196df3dbf4a002a693f750400894102e36cdc0baa62d1bfdddc0f0d8371969ad1dbe16496dc34fd2800a97ca450400894102e36cdc0baa62d1bff", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=2592000, public" + }, + { + "last-modified": "Thu, 01 Nov 2012 20:53:17 GMT" + }, + { + "date": "Thu, 01 Nov 2012 20:53:17 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "6344" + }, + { + "content-type": "text/javascript; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 01 Dec 2012 20:53:17 GMT" + } + ] + }, + { + "seqno": 9, + "wire": "88e5da6c96df697e9403ea6a225410022500e5c006e36153168dff6196df697e9403ea6a225410022500e5c006e36153168dffe0df0f0d03343733d4dee46496df3dbf4a01e5349fba820044a01cb800dc6c2a62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=2592000, public" + }, + { + "last-modified": "Tue, 09 Oct 2012 06:01:51 GMT" + }, + { + "date": "Tue, 09 Oct 2012 06:01:51 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "473" + }, + { + "content-type": "text/javascript; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Thu, 08 Nov 2012 06:01:51 GMT" + } + ] + }, + { + "seqno": 10, + "wire": "88e8dd6c96df697e9403ea6a225410022500e5c006e36da98b46ff6196df697e9403ea6a225410022500e5c006e36da98b46ffe3e20f0d8413c00bbfd7e1e76496df3dbf4a01e5349fba820044a01cb800dc6db53168df", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=2592000, public" + }, + { + "last-modified": "Tue, 09 Oct 2012 06:01:55 GMT" + }, + { + "date": "Tue, 09 Oct 2012 06:01:55 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "28017" + }, + { + "content-type": "text/javascript; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Thu, 08 Nov 2012 06:01:55 GMT" + } + ] + }, + { + "seqno": 11, + "wire": "88ebd3d2d1d0e30f0d83085b07cfe8ce", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "last-modified": "Mon, 23 Jun 2008 23:06:11 GMT" + }, + { + "cache-control": "public, max-age=315360000" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Tue, 09 Oct 2012 05:33:00 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "1150" + }, + { + "content-type": "text/plain" + }, + { + "server": "Apache" + }, + { + "expires": "Fri, 07 Oct 2022 05:33:00 GMT" + } + ] + }, + { + "seqno": 12, + "wire": "88eb588eaed8e8313e94a47e561cc581c0036c96dc34fd280654d27eea0801128166e32d5c1054c5a37f6196dc34fd280654d27eea0801128166e32d5c1054c5a37fe7e60f0d837dc701efe5eb6496dc34fd280654d27eea0801128166e34fdc1054c5a37f", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "public, max-age=600" + }, + { + "last-modified": "Sat, 03 Nov 2012 13:34:21 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:34:21 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "9660" + }, + { + "content-type": "text/html; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 03 Nov 2012 13:49:21 GMT" + } + ] + }, + { + "seqno": 13, + "wire": "88efe46c96df697e9403ea6a225410022500e5c106e32f298b46ff6196df697e9403ea6a225410022500e5c106e32f298b46ffeae90f0d82109bdee8ee6496df3dbf4a01e5349fba820044a01cb820dc65e53168df", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=2592000, public" + }, + { + "last-modified": "Tue, 09 Oct 2012 06:21:38 GMT" + }, + { + "date": "Tue, 09 Oct 2012 06:21:38 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "225" + }, + { + "content-type": "text/javascript; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Thu, 08 Nov 2012 06:21:38 GMT" + } + ] + }, + { + "seqno": 14, + "wire": "88f2e76c96df697e9403ea6a225410022500edc102e05e53168dff6196df697e9403ea6a225410022500edc102e05e53168dffedec0f0d8313cdbbe1ebf16496df3dbf4a01e5349fba820044a01db8205c0bca62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=2592000, public" + }, + { + "last-modified": "Tue, 09 Oct 2012 07:20:18 GMT" + }, + { + "date": "Tue, 09 Oct 2012 07:20:18 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "2857" + }, + { + "content-type": "text/javascript; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Thu, 08 Nov 2012 07:20:18 GMT" + } + ] + }, + { + "seqno": 15, + "wire": "88f5ea6c96df697e94640a6a2254100225041b8d82e36053168dff6196df697e94640a6a2254100225041b8d82e36053168dfff0ef0f0d830b2fbde4eef46496df3dbf4a09f5349fba820044a08371b05c6c0a62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=2592000, public" + }, + { + "last-modified": "Tue, 30 Oct 2012 21:50:50 GMT" + }, + { + "date": "Tue, 30 Oct 2012 21:50:50 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "1398" + }, + { + "content-type": "text/javascript; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Thu, 29 Nov 2012 21:50:50 GMT" + } + ] + }, + { + "seqno": 16, + "wire": "88f8ed6c96df3dbf4a002a693f750400894106e09cb8d3ca62d1bf6196df3dbf4a002a693f750400894106e09cb8d3ca62d1bff3f20f0d84680cb6cfe7f1f76496dc34fd2800a97ca450400894106e09cb8d3ca62d1bff", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=2592000, public" + }, + { + "last-modified": "Thu, 01 Nov 2012 21:26:48 GMT" + }, + { + "date": "Thu, 01 Nov 2012 21:26:48 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "40353" + }, + { + "content-type": "text/javascript; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 01 Dec 2012 21:26:48 GMT" + } + ] + }, + { + "seqno": 17, + "wire": "88408721eaa8a4498f57842507417ff16c96c361be940094d27eea080112820dc69cb82754c5a37f6196c361be940094d27eea080112820dc69cb82754c5a37ff7f60f0d83134dbd5f95497ca589d34d1f6a1271d882a60320eb3cf36fac1ff6768586b19272ff6496dd6d5f4a004a5f2914100225041b8d39704ea98b46ff", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=2592000, public" + }, + { + "last-modified": "Fri, 02 Nov 2012 21:46:27 GMT" + }, + { + "date": "Fri, 02 Nov 2012 21:46:27 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "2458" + }, + { + "content-type": "text/html; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Sun, 02 Dec 2012 21:46:27 GMT" + } + ] + }, + { + "seqno": 18, + "wire": "88c3f66c96d07abe940b6a6a2254100225042b8076e05d53168dff6196d07abe940b6a6a2254100225042b8076e05d53168dff5a839bd9ab7b8b84842d695b05443c86aa6f0f0d03373237f2408bf2b4b60e92ac7ad263d48f9d868a0fe16c361e95274a6b45c61894f65b4a17258334c8a0c84ae7b26fc46496e4593e940b4a693f75040089410ae01db81754c5a37f", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=2592000, public" + }, + { + "last-modified": "Mon, 15 Oct 2012 22:07:17 GMT" + }, + { + "date": "Mon, 15 Oct 2012 22:07:17 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "727" + }, + { + "content-type": "text/javascript; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Wed, 14 Nov 2012 22:07:17 GMT" + } + ] + }, + { + "seqno": 19, + "wire": "885f88352398ac74acb37fca5891aed8e8313e94a47e561cc5804dbe20001f798624f6d5d4b27f6196c361be940094d27eea080112820dc69cb82794c5a37fc9", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "close" + }, + { + "cache-control": "public, max-age=2592000" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Fri, 02 Nov 2012 21:46:28 GMT" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 20, + "wire": "88c1cdc0bfcbc9", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "close" + }, + { + "cache-control": "public, max-age=2592000" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Fri, 02 Nov 2012 21:46:27 GMT" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 21, + "wire": "88c1cdc0bfbec9", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "close" + }, + { + "cache-control": "public, max-age=2592000" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Fri, 02 Nov 2012 21:46:28 GMT" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 22, + "wire": "88c1cdc0bfcbc9", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "close" + }, + { + "cache-control": "public, max-age=2592000" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Fri, 02 Nov 2012 21:46:27 GMT" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 23, + "wire": "88cddd6c96dc34fd280654d27eea0801128166e32d5c0bca62d1bf6196dc34fd280654d27eea0801128166e32d5c0baa62d1bfc7c60f0d84081d781fccc5cb6496dc34fd280654d27eea0801128166e34fdc0bca62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "public, max-age=600" + }, + { + "last-modified": "Sat, 03 Nov 2012 13:34:18 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:34:17 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "10780" + }, + { + "content-type": "text/html; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Sat, 03 Nov 2012 13:49:18 GMT" + } + ] + }, + { + "seqno": 24, + "wire": "88d05891a47e561cc5804dbe20001f4a576c74189f6c96dc34fd280654d27eea0801128166e05db8d814c5a37f6196dc34fd280654d27eea0801128166e05db8d814c5a37fcbca0f0d83109a6fd0c9cf6496d07abe94032a5f291410022502cdc0bb71b0298b46ff", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=2592000, public" + }, + { + "last-modified": "Sat, 03 Nov 2012 13:17:50 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:17:50 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "2245" + }, + { + "content-type": "text/html; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 13:17:50 GMT" + } + ] + }, + { + "seqno": 25, + "wire": "88c8d4c7c66195d07abe941094d444a820044a0857000b810a98b46fd1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "close" + }, + { + "cache-control": "public, max-age=2592000" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Mon, 22 Oct 2012 22:00:11 GMT" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 26, + "wire": "88c9d5c8c76196dc34fd282754d444a820044a045700fdc036a62d1bffd2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "close" + }, + { + "cache-control": "public, max-age=2592000" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 27 Oct 2012 12:09:05 GMT" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 27, + "wire": "88cad6c9c86196dc34fd282754d444a820044a01db8cb9702253168dffd3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "close" + }, + { + "cache-control": "public, max-age=2592000" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 27 Oct 2012 07:36:12 GMT" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 28, + "wire": "88cbd7cac96195d07abe941094d444a820044a0857000b811298b46fd4", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "connection": "close" + }, + { + "cache-control": "public, max-age=2592000" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Mon, 22 Oct 2012 22:00:12 GMT" + }, + { + "server": "Apache" + } + ] + }, + { + "seqno": 29, + "wire": "88d85890aed8e8313e94a47e561cc5802d34007f6c96dc34fd280654d27eea0801128115c6d9b82654c5a37f6196dc34fd280654d27eea0801128115c6d9b82654c5a37fd3d20f0d836dd6595f92497ca589d34d1f6a1271d882a60b532acf7fd2d86496d07abe94032a5f2914100225022b8db3704ca98b46ff", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "public, max-age=14400" + }, + { + "last-modified": "Sat, 03 Nov 2012 12:53:23 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:53:23 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "5733" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 12:53:23 GMT" + } + ] + }, + { + "seqno": 30, + "wire": "88ddc26c96dc34fd280654d27eea0801128105c6deb8db6a62d1bf6196dc34fd280654d27eea0801128105c6deb8db6a62d1bfd7d60f0d830b4f03c1d5db6496d07abe94032a5f2914100225020b8dbd71b6d4c5a37f", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "public, max-age=14400" + }, + { + "last-modified": "Sat, 03 Nov 2012 10:58:55 GMT" + }, + { + "date": "Sat, 03 Nov 2012 10:58:55 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "1480" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 10:58:55 GMT" + } + ] + }, + { + "seqno": 31, + "wire": "88e0c56c96dc34fd280654d27eea080112807ee36cdc65e53168df6196dc34fd280654d27eea080112807ee36cdc65e53168dfdad90f0d84081a003fdfd8de6496d07abe94032a5f291410022500fdc6d9b8cbca62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "public, max-age=14400" + }, + { + "last-modified": "Sat, 03 Nov 2012 09:53:38 GMT" + }, + { + "date": "Sat, 03 Nov 2012 09:53:38 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "10400" + }, + { + "content-type": "text/html; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Mon, 03 Dec 2012 09:53:38 GMT" + } + ] + }, + { + "seqno": 32, + "wire": "88e3d06c96df697e9403ea6a225410022500e5c006e34ea98b46ff6196df697e9403ea6a225410022500e5c006e34ea98b46ffdddc0f0d8369e7035f94497ca582211f6a1271d882a60320eb3cf36fac1fdce26496df3dbf4a01e5349fba820044a01cb800dc69d53168df", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "max-age=2592000, public" + }, + { + "last-modified": "Tue, 09 Oct 2012 06:01:47 GMT" + }, + { + "date": "Tue, 09 Oct 2012 06:01:47 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "4861" + }, + { + "content-type": "text/css; charset=iso-8859-1" + }, + { + "x-frame-options": "Allow-From https://forums.craigslist.org" + }, + { + "server": "Apache" + }, + { + "expires": "Thu, 08 Nov 2012 06:01:47 GMT" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_25.json b/http/http-client/src/test/resources/hpack-test-case/story_25.json new file mode 100644 index 000000000..8e692bff5 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_25.json @@ -0,0 +1,9149 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "48826401768c86b19272ad78fe8e92b015c3a26c95dc34fd28ca9a4fdd410022502cdc0bd704da98b46f0f1f8f9d29aee30c78f1e172c63f4b90f4ff4085b283cc693fa9ada96d9957012f72cfd95700ab379566fa2954576bb5b73e46cbaab386302ae0160b23238ebcf01e6f0f0d01306196dc34fd280654d27eea0801128166e321b8db8a62d1bf", + "headers": [ + { + ":status": "301" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "etag": "" + }, + { + "last-modified": "Sat, 3 Nov 2012 13:18:25 GMT" + }, + { + "location": "http://www.ebay.com" + }, + { + "rlogid": "p4fug%60fvehq%60%3C%3Dsm%2Bpu56*a37%3Fb0%60-13ac6788085" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:31:56 GMT" + } + ] + }, + { + "seqno": 1, + "wire": "88c10f28ff1ba8f520a8418f5417a6870e886ecfba4a2ee9d3be16f4efc3d398b65ba2f35e73f4c16e8dfb1bcfd345ba2f35e61d0787261a9dc7d43d34f4235aafe9a746fc1ef9efbb3e9dfcdbd3d36d1a7a6c173f7fb4fed3867d1cb0f4d1b2fe7861c3b28ddaf8e8fc7ad6ff5ef9fb52f9e919aa8172c63f4b90f4fda983cd66b0a88375b57d280656d27eeb08016540b37190dc6dd53168dff6a6b1a678185885aec3771a4b4085aec1cd48ff86a8eb10649cbf5a839bd9ab5f91497ca589d34d1f649c7620a98386fc2b3d0f0d840b410b5fc2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "nonsession=CgAFMABhSdlBNNTA5NTFjY2QuMC4xLjEuMTQ5LjMuMC4xAMoAIFn7Hk1jNjc4ODNmMTEzYTBhNTY5NjRlNjQ2YzZmZmFhMWFjMQDLAAFQlSPVMX8u5Z8*; Domain=.ebay.com; Expires=Sun, 03-Nov-2013 13:31:57 GMT; Path=/" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-length": "14114" + }, + { + "date": "Sat, 03 Nov 2012 13:31:56 GMT" + } + ] + }, + { + "seqno": 2, + "wire": "886196dc34fd280654d27eea0801128166e321b8dbca62d1bf768586b19272ff6c96c361be940094d27eea0801128005c03b704253168dff0f1394fe5b6de005a588465668923ae96375b189e07f3f52848fd24a8f0f0d83644e3b4087f2b12a291263d5842507417f5f88352398ac74acb37f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:31:58 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Fri, 02 Nov 2012 00:07:22 GMT" + }, + { + "etag": "\"558014-cc3-4cd77eb75a280\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "3267" + }, + { + "x-cnection": "close" + }, + { + "content-type": "image/jpeg" + } + ] + }, + { + "seqno": 3, + "wire": "88cb7f0aa6adaa9570165bd25b64a3c11566fbfdd3f29438eaf305b28d90ac1646471d79e6c8e2c0f211870f28faaab31a08c935a6918238ebcf364702c8c0300df95c6dc7a30b92cadbe174a56c4eb8d81a2fff899ad348c11c75e799942164601b6e3ee34571a708e4b28c611902d89d71b0345fff3ed4be7a466aa05cb18fd2e43d3f6a60f359ac2a20dd6d5f4a0195b49fbac20059502cdc64371b794c5a37fda9ac699e063f4003703370d2acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5ee1b46a437f40d4bf8388d4d7baf9d4d7ba11a9ab86d53743a0ea64d37d4e1a72297b568534c3c54c9a77a9bb7c2a5fc1a14d7b707f3588caec3771a4bf4a547588324e5c95f87352398ac4c697f0f0d0234326196dc34fd280654d27eea0801128166e321b8dbaa62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4n%60rujfudlwc%3D9vt*ts67.g15ea31-13ac67885c6-0x1a1" + }, + { + "set-cookie": "npii=bcguid/c67885c613a0a0a9f6568b16ff5917ee5276504e^tguid/c67883f113a0a56964e646c6ffaa1ac15276504e^; Domain=.ebay.com; Expires=Sun, 03-Nov-2013 13:31:58 GMT; Path=/" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa ADMa DEVa PSDo PSAa OUR SAMo IND UNI COM NAV INT STA DEM PRE\"" + }, + { + "cache-control": "private, no-cache" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "42" + }, + { + "date": "Sat, 03 Nov 2012 13:31:57 GMT" + } + ] + }, + { + "seqno": 4, + "wire": "88c86496dc34fd280654d27eea080112816ee321b8d36a62d1bf6c96dc34fd28171486d9941000ca8205c685704ea98b46ffc1c70f1391fe5e04b1b8f95d65c71a2321b8e4a5fe7f768dd06258741e54ad9326e61c5c1f0f0d023439", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:31:58 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 15:31:45 GMT" + }, + { + "last-modified": "Sat, 16 Aug 2003 20:42:27 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80fb69e73664c31:6fe\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "49" + } + ] + }, + { + "seqno": 5, + "wire": "88cb6c96df3dbf4a05e535112a0801128266e36cdc6dd53168df5f87352398ac5754dfca0f1390fe5e005e66491c7a31c8490371d103f9c00f0d83136cbf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:31:58 GMT" + }, + { + "last-modified": "Thu, 18 Oct 2012 23:53:57 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80183dd68badcd1:720\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "2539" + } + ] + }, + { + "seqno": 6, + "wire": "88cd6496d07abe940814be522820044a085702d5c6c0a62d1bff6c96df697e940814cb6d0a0801128005c0bd71b714c5a37fc6ccc2588ba47e561cc581979e7800070f0d82105f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:31:58 GMT" + }, + { + "expires": "Mon, 10 Dec 2012 22:14:50 GMT" + }, + { + "last-modified": "Tue, 10 Jul 2012 00:18:56 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "219" + } + ] + }, + { + "seqno": 7, + "wire": "88d06c96df697e940814cb6d0a0801128005c0bb71b654c5a37fc8ce0f1390fe5e03c5740e8990b652481b8dca1fe7c40f0d821003", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:31:58 GMT" + }, + { + "last-modified": "Tue, 10 Jul 2012 00:17:53 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"808e7072315ecd1:5f1\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "201" + } + ] + }, + { + "seqno": 8, + "wire": "88d16c96e4593e940bca65b6850400894102e32cdc65953168dfc9cfc50f0d830b8267", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:31:58 GMT" + }, + { + "last-modified": "Wed, 18 Jul 2012 20:33:33 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "1623" + } + ] + }, + { + "seqno": 9, + "wire": "88da7f0da6adaaeb9e4a3c11566fbfde8f94ceabb8631c8abb85d12acdf582c8c8e3af3cf360581e42e4bf5890aec3771a4bf4a523f2b0e62c0f38d0017f0ec7bdae0fe6f70da3521bfa06a5fc1c46a6bdd09d4d7baf9d4d5c36a97786e53869c8a6be1b54c9a77a97f0685376f854d7b70297b568534c3c54d5bef29a756452feed6a5ed5b7f9cc0f0d023433d5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9vl*th%7Fbad%7F72%3D-13ac6788850-0x16f" + }, + { + "cache-control": "private, max-age=86400" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:31:58 GMT" + } + ] + }, + { + "seqno": 10, + "wire": "88dd7f01a4adaaeb9e4a3c11566fbfdcbf29544f3ad3aab802aaee07160b23238ebcf3822ac0f32c7fc0bfcd0f0d023433cc", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9ve*t%28747%60e%7E6-13ac678862e-0xfb" + }, + { + "cache-control": "private, max-age=86400" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:31:57 GMT" + } + ] + }, + { + "seqno": 11, + "wire": "88de7ea4adaaeb9e4a3c11566fbfdcbf29544f3ad3aab802aaee08d60b23238ebcf38e8160790b41c1c0ce0f0d023433cd", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9ve*t%28747%60e%7Eb-13ac6788670-0x141" + }, + { + "cache-control": "private, max-age=86400" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:31:57 GMT" + } + ] + }, + { + "seqno": 12, + "wire": "88df7ea3adaaeb9e4a3c11566fbf6aaee0f94aa279d6c1301dad60b236a365e201c2ac0f21703f6c96df697e9403ea6a225410022504cdc6c171b754c5a37fc2d00f0d83684117588ca47e561cc5804fb4e3c265bf6496df3dbf4a040a6a22541002ca816ee01fb81654c5a37fdb408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9u%7E*t%28750g07p-13a4b38c06e-0x161" + }, + { + "last-modified": "Tue, 09 Oct 2012 23:50:57 GMT" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "4212" + }, + { + "cache-control": "max-age=29468235" + }, + { + "expires": "Thu, 10 Oct 2013 15:09:13 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:58 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 13, + "wire": "88e47f03a5adaaeb9e4a3c11566fbf6aaee0f94aa279d6c1301d559bab0591b8d95d24919160790b91ff6c96d07abe940b6a6a225410022502fdc086e05c53168dffc7d50f0d836dc705588ca47e561cc5804fbe16df103f6496df697e940b6a6a22541002ca817ee320b8cbca62d1bfe0c2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9u%7E*t%28750g07%3B-13a65e7cdbc-0x16b" + }, + { + "last-modified": "Mon, 15 Oct 2012 19:11:16 GMT" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "5662" + }, + { + "cache-control": "max-age=29915920" + }, + { + "expires": "Tue, 15 Oct 2013 19:30:38 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:58 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 14, + "wire": "88e87f02a3adaaeb9e4a3c11566fbf6aaee0f94aa279d6c1301da560b238e491a7d991b581e42e496c96df3dbf4a002a693f750400894102e32fdc69953168dfcbd90f0d837c2f35588ca47e561cc58190b2f840d35f6496c361be940054d27eea0801654106e05cb801298b46ffe4c6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9u%7E*t%28750g07m-13abdd493d5-0x16d" + }, + { + "last-modified": "Thu, 01 Nov 2012 20:39:43 GMT" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "9184" + }, + { + "cache-control": "max-age=31391044" + }, + { + "expires": "Fri, 01 Nov 2013 21:16:02 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:58 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 15, + "wire": "88ece66c96df697e94642a651d4a0801128015c6deb8cb4a62d1bf5f90497ca582211f649c7620a98386fc2b3d0f0d83644f0b588ca47e561cc5804cb2cb217dbf6497e4593e94642a65b6850400b2a05ab8dbd719694c5a37ff6196dc34fd280654d27eea0801128166e321b8dbea62d1bfcb7b8b84842d695b05443c86aa6f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 31 Jan 2012 02:58:34 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-length": "3282" + }, + { + "cache-control": "max-age=23333195" + }, + { + "expires": "Wed, 31 Jul 2013 14:58:34 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:59 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 16, + "wire": "88f2ec6c96df3dbf4a01c53716b504008940b971b0dc642a62d1bf5f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ec938ec4153070df8567bf0f0d836da681588ca47e561cc5804e36cb8eba1f6496c361be94038a6e2d6a08016540b971b0dc640a62d1bfc3d0c2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 06 Sep 2012 16:51:31 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "5440" + }, + { + "cache-control": "max-age=26536771" + }, + { + "expires": "Fri, 06 Sep 2013 16:51:30 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:59 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 17, + "wire": "88f66c96df3dbf4a042a6a225410022502fdc0b7702fa98b46ffea0f0d836c2d3d588ca47e561cc58190b4dbcebcff6496dc34fd280129a4fdd41002ca8172e01bb80794c5a37fc6d3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 11 Oct 2012 19:15:19 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5148" + }, + { + "cache-control": "max-age=31458789" + }, + { + "expires": "Sat, 02 Nov 2013 16:05:08 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:59 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 18, + "wire": "88768c86b19272ad78fe8e92b015c36c96d07abe9413aa6e2d6a080102807ee01db82694c5a37fcb5b842d4b70ddc8f60f0d836dc75f5892aed8e8313e94a47e561cc58190b6cb80003f6497dd6d5f4a0195349fba820059502cdc64371b7d4c5a37ffcbd8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 27 Sep 2010 09:07:24 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "5679" + }, + { + "cache-control": "public, max-age=31536000" + }, + { + "expires": "Sun, 03 Nov 2013 13:31:59 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:59 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 19, + "wire": "88c26c96e4593e94642a6a225410022502fdc0bf71b654c5a37ff20f0d83742d83588ca47e561cc58190b4e3cdb2cf6496dc34fd280129a4fdd41002ca817ae34edc644a62d1bfcedb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Wed, 31 Oct 2012 19:19:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7150" + }, + { + "cache-control": "max-age=31468533" + }, + { + "expires": "Sat, 02 Nov 2013 18:47:32 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:59 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 20, + "wire": "88c55a839bd9ab6c96e4593e94134a6a225410022500ddc6c371a0a98b46ffd30f0d836dc75f588ca47e561cc5819038d34cbc2f6496df3dbf4a09a535112a080165403771b0dc682a62d1bfd2dfd1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Wed, 24 Oct 2012 05:51:41 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-length": "5679" + }, + { + "cache-control": "max-age=30644382" + }, + { + "expires": "Thu, 24 Oct 2013 05:51:41 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:59 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 21, + "wire": "88c96c96df3dbf4a002a693f750400894106e09ab8cbea62d1bf5f88352398ac74acb37f0f0d836dc65c588ca47e561cc58190b4d802f3bf6496dc34fd280129a4fdd41002ca8166e341b8d38a62d1bfd6e3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 01 Nov 2012 21:24:39 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5636" + }, + { + "cache-control": "max-age=31450187" + }, + { + "expires": "Sat, 02 Nov 2013 13:41:46 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:59 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 22, + "wire": "88cd6c96df3dbf4a002a693f750400894133700cdc132a62d1bfc10f0d83742ebb588ca47e561cc58190b4f3cd841f6496dd6d5f4a0195349fba8200595000b8205c13ea62d1bfd9e6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 01 Nov 2012 23:03:23 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7177" + }, + { + "cache-control": "max-age=31488510" + }, + { + "expires": "Sun, 03 Nov 2013 00:20:29 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:59 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 23, + "wire": "88d06c96df3dbf4a002a693f7504008940bd71a0dc65e53168dfc40f0d8379d00b588ca47e561cc58190b220b4067f6496c361be940054d27eea0801654006e36ddc1094c5a37fdce9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 01 Nov 2012 18:41:38 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "8702" + }, + { + "cache-control": "max-age=31321403" + }, + { + "expires": "Fri, 01 Nov 2013 01:55:22 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:59 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 24, + "wire": "88d36c96c361be940894d444a820044a05ab827ee32fa98b46ffc70f0d840b2069cf588ca47e561cc5804fb8cbaeb4e76496dc34fd281129a88950400b2a05ab816ae09b53168dffdfec", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Fri, 12 Oct 2012 14:29:39 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "13046" + }, + { + "cache-control": "max-age=29637746" + }, + { + "expires": "Sat, 12 Oct 2013 14:14:25 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:59 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 25, + "wire": "88d67f28a7adaaeb9e4a3c11566fbf6aaee0f94d2e32baacde7447a5c559c0b0591c648d96df65d581e42d176c96df697e94640a6a2254100225041b8076e32d298b46fff5cb0f0d84700f81cf588ca47e561cc58190b2e880d3ff6496c361be940054d27eea08016540b771b7ee09d53168df6196dc34fd280654d27eea0801128166e321b8dbca62d1bff1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9u%7E*tm63.%3C72om6%3E-13abcb35937-0x14e" + }, + { + "last-modified": "Tue, 30 Oct 2012 21:07:34 GMT" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "60906" + }, + { + "cache-control": "max-age=31372049" + }, + { + "expires": "Fri, 01 Nov 2013 15:59:27 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:58 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 26, + "wire": "88dbd36c96df697e940bca6e2d6a080112800dc082e09f53168dffe20f0d846842037f588ca47e561cc5804eb61742107f6496e4593e940bca6e2d6a0801654006e041704fa98b46ffe7f4e6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 18 Sep 2012 01:10:29 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "42205" + }, + { + "cache-control": "max-age=27517110" + }, + { + "expires": "Wed, 18 Sep 2013 01:10:29 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:59 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 27, + "wire": "88dedde4dcd60f0d846d965b7fe65892aec3771a4bf4a523f2b0e62c0c85b65c0001dbe8f5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 27 Sep 2010 09:07:24 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "53359" + }, + { + "vary": "Accept-Encoding" + }, + { + "cache-control": "private, max-age=31536000" + }, + { + "expires": "Sun, 03 Nov 2013 13:31:59 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:31:59 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 28, + "wire": "886196dc34fd280654d27eea0801128166e322b800a98b46ff6c96df697e940b8a6a225410022502f5c03971b7d4c5a37f5f87352398ac5754df52848fd24a8f768dd06258741e54ad9326e61c5c1f0f0d83136f330f1390fe5e015928de23e38c9206e38cbffcff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:01 GMT" + }, + { + "last-modified": "Tue, 16 Oct 2012 18:06:59 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "2583" + }, + { + "etag": "\"80e3ea8c9abcd1:639\"" + } + ] + }, + { + "seqno": 29, + "wire": "48826402e57f0da4adaaeb9e4a3c11566fbfde8f94ceabb8631c8abb85d08160b23238ebcf8a56560790b21f5886a8eb10649cbf4003703370c7bdae0fe6f70da3521bfa06a5fc1c46a6bdd09d4d7baf9d4d5c36a97786e53869c8a6be1b54c9a77a97f0685376f854d7b70297b568534c3c54d5bef29a756452feed6a5ed5b7f95f87352398ac4c697f0f0d0130c76401300f28bdd7ba0deb83ed4be7a466aa0a466a972c63f562695c87a7ed4c1e6b3585441badabe94032b693f758400b2a059b8c8ae002a62d1bfed4d634cf0316269f0f1fffda029d29aee30c22cf2bd23354b9631fab134ae43d2c589a7fcda9a6f5327c0e0e883d5f1441ffaffd4517febff5145ffaffd7c4d011c75e799942164601b6e3ee34571a708e4b28c611903f048038da46486186186186186e8b578e565ed976f6275acfdf46abd467fcdacfea09d697a62f7cfb5add82dcb3f96fd6e9347199ceb65bd3f075aa2baf75d17efdf5458551610bdef365ed92cf58737a86fd7371dfd796dd9c68478dd8b9aa2bbabde2e999ed328551612e9df3efbdb1fa40cefc58bd32d61a45ac1af84d0bbe78bd0f6c5eb37b9e571acbba7e6a8b0f87129be0d596ee2e79bf1db7cb2d0ff71e6c593d6fa238e5df689fdcfe6da0cf7f72a2c25237eb509dbb9f321515debd72f8709dfa61b71aa2bbaabcf66c2cfd71b762a2ba3b45de1f793975f39ba666f77667317479437dfdd3cdf04b47554587d8b7bf7bb96671cf10c30c2ab37d566ffc570042d89db810b627ae042d89ff890d0042d89db810b627ae042d89ff8ef035f05a89070df8567be23a6013ce3c077e0f64900596c2fb4fb620b6f03e09340471d79e6c8e059180601bf2b8db8f46172595b7c2e94bf048e0efd0ebc889571a105a63a3d2fc7a5ea0c5a930a105a63a0b62f110745118c9d41f1177b24a600b2d85f69f6c416de0fc5907a2a3", + "headers": [ + { + ":status": "302" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9vl*th%7Fbad%7F710-13ac67892f3-0x131" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:32:01 GMT" + }, + { + "expires": "0" + }, + { + "set-cookie": "PS=T.0; Domain=main.ebayrtm.com; Expires=Sun, 03-Nov-2013 13:32:01 GMT; Path=/rtm" + }, + { + "location": "http://srx.main.ebayrtm.com/rtm?RtmCmd&a=json&l=@@__@@__@@&g=c67883f113a0a56964e646c6ffaa1ac1&c=1H4sIAAAAAAAAAB2OwWrCQBCG74LvMOClLXR3Zsckm8gevLR4SEuJhx5ySdMVg6krujXap%2B8kMDDD%2F%2F18zKJqIryFKyADpgVTkWRQVlswSGY%2BOzGjK8Nf1%2FeNThTCQ9m03TGGy34Fm2P0PUgA7xV8AqGyKzhf64JShY%2Fw6ttD0OJBGYKX7ux34aZHKGIyTlbbfTu29S9KR0LDS%2Fec5yO27BLKs%2BkkJw6cvjFuH%2BOpLrQehkH5r%2Bau2vAzIWkxKjK5Sq3KeMxs5vzmY90flk%2Fz2T9Cveg66wAAAA%3D%3D&p=11527:11528:11529&di=11527:11528:11529&v=4&enc=UTF-8&bm=286807&ord=1351949521580&cg=c67885c613a0a0a9f6568b16ff5917ee&cb=vjo.dsf.assembly.VjClientAssembler._callback0&_vrdm=1351949521581&r=yes" + } + ] + }, + { + "seqno": 30, + "wire": "88c86496d07abe940814be522820044a00571a05c13ca62d1bff6c97df3dbf4a01f532db4282001f502f5c659b8cbea62d1bffc8c7c6588ba47e561cc581979e7800070f0d830b4073", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:01 GMT" + }, + { + "expires": "Mon, 10 Dec 2012 02:40:28 GMT" + }, + { + "last-modified": "Thu, 09 Jul 2009 18:33:39 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "1406" + } + ] + }, + { + "seqno": 31, + "wire": "88cb6497dd6d5f4a09b5349fba820044a099b8d3971b714c5a37ff6c96d07abe940b2a65b68504003ea08571a66e32ca98b46fc4ca0f138ffe5e005e8c6dbf1b44186e3720bf9fc9c00f0d03333836", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:01 GMT" + }, + { + "expires": "Sun, 25 Nov 2012 23:46:56 GMT" + }, + { + "last-modified": "Mon, 13 Jul 2009 22:43:33 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"8018ba59b4ca1:5d2\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "386" + } + ] + }, + { + "seqno": 32, + "wire": "88cd6c97df3dbf4a01f532db4282001f502f5c659b8d054c5a37ffcccbca0f0d83642db70f1390fe5e015928de23e38c9206e38cbffcff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:01 GMT" + }, + { + "last-modified": "Thu, 09 Jul 2009 18:33:41 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "3155" + }, + { + "etag": "\"80e3ea8c9abcd1:639\"" + } + ] + }, + { + "seqno": 33, + "wire": "88f07f09a4adaaeb9e4a3c11566fbfde8f94ceabb8631c8abb85d0b6b059191c75e7d96c2b03c8443fc8c7f70f0d83699103cfc50f28ddc7be00b2d85f69f6c416de02a01042d89f540d09e75e75c540e09c0b2d36a819085b13ca81a13ce3c26550382700d34caa064216c4eaa0684f38f002a81c10197c0f7da97cf48cd54148cd52e58c7eac4d2b90f4fda9ac699e062c4d3fe9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9vl*th%7Fbad%7F715-13ac6789351-0x12a" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "4320" + }, + { + "date": "Sat, 03 Nov 2012 13:32:01 GMT" + }, + { + "expires": "0" + }, + { + "set-cookie": "HT=1351949521580%0211529%04287876%06261345%0311528%04286823%06260443%0311527%04286801%06203908; Domain=main.ebayrtm.com; Path=/rtm" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 34, + "wire": "88cf6496d07abe94138a693f750400894006e01db8d3ea62d1bf6c96c361be94036a6a225410022502f5c69eb81714c5a37fcfcecdc40f0d847c4dbad7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:01 GMT" + }, + { + "expires": "Mon, 26 Nov 2012 01:07:49 GMT" + }, + { + "last-modified": "Fri, 05 Oct 2012 18:48:16 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "92574" + } + ] + }, + { + "seqno": 35, + "wire": "88d16497dd6d5f4a09b5349fba820044a099b8d3971b6d4c5a37ff6c96df3dbf4a09f5340ec5040089410ae32e5c0014c5a37fd1d00f138ffe40d3c3236094921240dc700cff3fcfc60f0d8465b089ef", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:01 GMT" + }, + { + "expires": "Sun, 25 Nov 2012 23:46:55 GMT" + }, + { + "last-modified": "Thu, 29 Mar 2012 22:36:00 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"048ac50fcdcd1:603\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "35128" + } + ] + }, + { + "seqno": 36, + "wire": "88f57f03a3adaaeb9e4a3c11566fbf6aaee0f94aa279d6c1301da560b2cca02b7296422c0f21685f6c96df697e9413ea693f7504008540bf702fdc0baa62d1bfcd5f86497ca582211f0f0d03353435588ca47e561cc5819036e32c81bf6496e4593e94132a6a22541002ca8076e081704e298b46ffd8408721eaa8a4498f5788ea52d6b0e83772fff37b8b84842d695b05443c86aa6f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9u%7E*t%28750g07m-133f0e5fedc-0x142" + }, + { + "last-modified": "Tue, 29 Nov 2011 19:19:17 GMT" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "text/css" + }, + { + "content-length": "545" + }, + { + "cache-control": "max-age=30563305" + }, + { + "expires": "Wed, 23 Oct 2013 07:20:26 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:01 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 37, + "wire": "886196dc34fd280654d27eea0801128166e322b801298b46ff6496df697e940bca5f2914100225000b826ee01e53168dff6c96c361be940094d27eea0801128005c139700253168dff768c86b19272ad78fe8e92b015c354012ad2f40f0d84109a13df", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:02 GMT" + }, + { + "expires": "Tue, 18 Dec 2012 00:25:08 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 00:26:02 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "22428" + } + ] + }, + { + "seqno": 38, + "wire": "88c26496df697e940bca5f2914100225000b826ee01b53168dff6c96c361be940094d27eea0801128005c13d704f298b46ffc1c0d4f60f0d8465979f6f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:02 GMT" + }, + { + "expires": "Tue, 18 Dec 2012 00:25:05 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 00:28:28 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "33895" + } + ] + }, + { + "seqno": 39, + "wire": "88c46496d07abe940baa5f291410022502e5c03f71b6d4c5a37ff1c2c1d5f70f0d8465e75c0f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:02 GMT" + }, + { + "expires": "Mon, 17 Dec 2012 16:09:55 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 18:41:38 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "38761" + } + ] + }, + { + "seqno": 40, + "wire": "88c56496d07abe940baa5f291410022502ddc002e09a53168dff6c96c361be940894d444a820044a05ab827ae34e298b46ffc4c3d7f90f0d84700075ef", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:02 GMT" + }, + { + "expires": "Mon, 17 Dec 2012 15:00:24 GMT" + }, + { + "last-modified": "Fri, 12 Oct 2012 14:28:46 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "60078" + } + ] + }, + { + "seqno": 41, + "wire": "88c47f0fa5adaaeb9e4a3c11566fbf6aaee0f94aa279d6c122aee1132b0591c7236fbe408560790b8eff6c96d07abe9413ea6a2254100225040b816ae040a62d1bffde5f88352398ac74acb37f0f0d84744e05ff588ca47e561cc58190b2f09f65ef6496c361be940054d27eea08016540bf7000b8dbea62d1bfe9ce", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9u%7E*t%28750d%7F23-13abd599c11-0x167" + }, + { + "last-modified": "Mon, 29 Oct 2012 20:14:10 GMT" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "72619" + }, + { + "cache-control": "max-age=31382938" + }, + { + "expires": "Fri, 01 Nov 2013 19:00:59 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:01 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 42, + "wire": "88c97f03a7adaaeb9e4a3c11566fbf6aaee0f94d2e32baacde7447a5c559c0b059190401c71b61581e42d13f6c96c361be940094d27eea0801128172e362b8db4a62d1bfe3c20f0d8471f7800f588ca47e561cc58190b4e05c65bf6496dc34fd280129a4fdd41002ca8172e362b8cb8a62d1bfedd2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9u%7E*tm63.%3C72om6%3E-13ac20abb51-0x14c" + }, + { + "last-modified": "Fri, 02 Nov 2012 16:52:54 GMT" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "69800" + }, + { + "cache-control": "max-age=31461635" + }, + { + "expires": "Sat, 02 Nov 2013 16:52:36 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:01 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 43, + "wire": "88cd0f28ff1ba8f520a8418f5417a6870e886ecfba4a2ee9d3be16f4efc3d398b65ba2f35e73f4c16e8dfb1bcfd345ba2f35e61d0787261a9dc7d43d34f4235aafe9a746fc1ef9efbb3e9dfcdbd3d36d1a7a6c173f7fb4fed3867d1cb0f4d1b2fe7861c3b28ddaf8e8fc7ad6ff5ef9fb52f9e919aa8172c63f4b90f4fda983cd66b0a88375b57d280656d27eeb08016540b37190dc6dd53168dff6a6b1a678185885aec3771a4b4085aec1cd48ff86a8eb10649cbf5a839bd9ab5f89352398ac7958c43d5f0f0d83085b076196dc34fd280654d27eea0801128166e322b80714c5a37f6c96d07abe941094d444a820044a099b806ae044a62d1bff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "nonsession=CgAFMABhSdlBNNTA5NTFjY2QuMC4xLjEuMTQ5LjMuMC4xAMoAIFn7Hk1jNjc4ODNmMTEzYTBhNTY5NjRlNjQ2YzZmZmFhMWFjMQDLAAFQlSPVMX8u5Z8*; Domain=.ebay.com; Expires=Sun, 03-Nov-2013 13:31:57 GMT; Path=/" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "image/x-icon" + }, + { + "content-length": "1150" + }, + { + "date": "Sat, 03 Nov 2012 13:32:06 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 23:04:12 GMT" + } + ] + }, + { + "seqno": 44, + "wire": "886196dc34fd280654d27eea0801128166e322b80754c5a37f6496dc34fd280654d27eea080112816ee321b8d36a62d1bf6c96dc34fd28171486d9941000ca8205c685704ea98b46ffedf3f2e90f0d0234390f1391fe5e04b1b8f95d65c71a2321b8e4a5fe7f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 15:31:45 GMT" + }, + { + "last-modified": "Sat, 16 Aug 2003 20:42:27 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "49" + }, + { + "etag": "\"80fb69e73664c31:6fe\"" + } + ] + }, + { + "seqno": 45, + "wire": "88c0e26c96df3dbf4a05e535112a0801128266e36cdc6dd53168dff5f40f1390fe5e005e66491c7a31c8490371d103f9f3ea0f0d83136cbf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "expires": "Sun, 25 Nov 2012 23:46:55 GMT" + }, + { + "last-modified": "Thu, 18 Oct 2012 23:53:57 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80183dd68badcd1:720\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "2539" + } + ] + }, + { + "seqno": 46, + "wire": "88c16c96df3dbf4a320532db4282001f504cdc683704fa98b46feff5f40f0d0235330f1390fe5e015928de23e38c9206e38cbffcff6496dc34fd280654d27eea080112816ee05ab82794c5a37f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "last-modified": "Thu, 30 Jul 2009 23:41:29 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "53" + }, + { + "etag": "\"80e3ea8c9abcd1:639\"" + }, + { + "expires": "Sat, 03 Nov 2012 15:14:28 GMT" + } + ] + }, + { + "seqno": 47, + "wire": "88c36496d07abe94138a693f75040089403771b72e34153168df6c97df3dbf4a320532db4282001f504cdc683719654c5a37fff2f8f7ee0f0d0235330f1391fe5e046d4b234d3928424186e3ceb9fcff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "expires": "Mon, 26 Nov 2012 05:56:41 GMT" + }, + { + "last-modified": "Thu, 30 Jul 2009 23:41:33 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "53" + }, + { + "etag": "\"80b4fd446f11ca1:876\"" + } + ] + }, + { + "seqno": 48, + "wire": "88db0f28bca2d275f4fc017db7de7c1f6a5f3d2335502e58c7e9721e9fb53079acd615106f9edfa50025b49fbac2005d502cdc645700ea98b46ffb5358d33c0c7fcbcac95f91497ca589d34d1f649c7620a98386fc2b3d0f0d84134db8efc6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lucky9=1959890; Domain=.ebay.com; Expires=Thu, 02-Nov-2017 13:32:07 GMT; Path=/" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-length": "24567" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + } + ] + }, + { + "seqno": 49, + "wire": "88c66496dd6d5f4a05c52f948a0801128176e361b8d3ea62d1bf6c96dd6d5f4a09c5309635040089403b71a05c0014c5a37fdeddf1d50f0d830bee3d", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "expires": "Sun, 16 Dec 2012 17:51:49 GMT" + }, + { + "last-modified": "Sun, 26 Feb 2012 07:40:00 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1968" + } + ] + }, + { + "seqno": 50, + "wire": "88c86496dd6d5f4a01a5349fba820044a05cb8cbb71b0298b46f6c96df697e9403aa65b68504003ea081702f5c684a62d1bff752848fd24a8f768dd06258741e54ad9326e61c5c1ff50f0d033631330f1391fe5e04b1b8f95d65c71a2321b8e4a5fe7f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "expires": "Sun, 04 Nov 2012 16:37:50 GMT" + }, + { + "last-modified": "Tue, 07 Jul 2009 20:18:42 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "613" + }, + { + "etag": "\"80fb69e73664c31:6fe\"" + } + ] + }, + { + "seqno": 51, + "wire": "88cc6497dd6d5f4a09b5349fba820044a099b8d3971b754c5a37ff6c96df3dbf4a099521b66504003aa08171a05c1094c5a37f5f87352398ac4c697fc20f1390fe5e005e66491c7a31c8490371d103f9c1f80f0d82109f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "expires": "Sun, 25 Nov 2012 23:46:57 GMT" + }, + { + "last-modified": "Thu, 23 Aug 2007 20:40:22 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80183dd68badcd1:720\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "229" + } + ] + }, + { + "seqno": 52, + "wire": "88cfc96c96df3dbf4a01a535112a0800754106e34d5c65c53168dfbfc3c2f90f0d830bcf3b0f138ffe4118e4688124ae11e0dc71e17f3f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "expires": "Mon, 26 Nov 2012 05:56:41 GMT" + }, + { + "last-modified": "Thu, 04 Oct 2007 21:44:36 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "1887" + }, + { + "etag": "\"0bad4c1cf6c81:682\"" + } + ] + }, + { + "seqno": 53, + "wire": "88d0cfcebfc30f1391fe5e04b1b8f95d65c71a2321b8e4a5fe7fc20f0d023439", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 15:31:45 GMT" + }, + { + "last-modified": "Sat, 16 Aug 2003 20:42:27 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80fb69e73664c31:6fe\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "49" + } + ] + }, + { + "seqno": 54, + "wire": "88d06496d07abe940bea693f75040089410ae0817191298b46ff6c96dc34fd28265486bb1410021500fdc65db8d014c5a37fe8e7588ba47e561cc581979e780007e00f0d8365f65c", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "expires": "Mon, 19 Nov 2012 22:20:32 GMT" + }, + { + "last-modified": "Sat, 23 Apr 2011 09:37:40 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3936" + } + ] + }, + { + "seqno": 55, + "wire": "88d36496df3dbf4a05952f948a080112817ae34cdc6da53168df6c96df3dbf4a01f53716b504008140bb7190dc640a62d1bfebeac0e20f0d83680dbf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "expires": "Thu, 13 Dec 2012 18:43:54 GMT" + }, + { + "last-modified": "Thu, 09 Sep 2010 17:31:30 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4059" + } + ] + }, + { + "seqno": 56, + "wire": "88d56496df3dbf4a05952f948a080112817ae09ab8cbca62d1bf6c96df3dbf4a01f53716b504008140bb702ddc03ca62d1bfedecc2e40f0d836997dd", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "expires": "Thu, 13 Dec 2012 18:24:38 GMT" + }, + { + "last-modified": "Thu, 09 Sep 2010 17:15:08 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4397" + } + ] + }, + { + "seqno": 57, + "wire": "88eddb6c96df3dbf4a05f5328ea50400894006e09cb801298b46ff5f90497ca582211f649c7620a98386fc2b3d0f0d840804d3df588ca47e561cc5804213e07997ff6496c361be940bea65b6850400b2a059b8272e01c53168dfdbf6f5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 19 Jan 2012 01:26:02 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-length": "10248" + }, + { + "cache-control": "max-age=22290839" + }, + { + "expires": "Fri, 19 Jul 2013 13:26:06 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 58, + "wire": "88f1df6c96df3dbf4a040a693f7504008540b3704edc682a62d1bfc10f0d830bafb5588ca47e561cc5802e09e702db9f6496dc34fd2810a9a07e941002ca800dc13d700ca98b46ffdef9f8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 10 Nov 2011 13:27:41 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-length": "1794" + }, + { + "cache-control": "max-age=16286156" + }, + { + "expires": "Sat, 11 May 2013 01:28:03 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 59, + "wire": "88f46c96dc34fd2817d4d03f4a0801128166e081702253168dffec0f0d8310190f588ba47e561cc581a65b782f036496d07abe94134a5f2914100225000b807ae09d53168dffe1408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Sat, 19 May 2012 13:20:12 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2031" + }, + { + "cache-control": "max-age=4358180" + }, + { + "expires": "Mon, 24 Dec 2012 00:08:27 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 60, + "wire": "88f86c96e4593e94642a6a225410022502d5c035702ca98b46fff00f0d83132fb5588ca47e561cc58190b6cb2fb6d76496dd6d5f4a0195349fba8200595022b8dbd700153168dfe5c1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Wed, 31 Oct 2012 14:04:13 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2394" + }, + { + "cache-control": "max-age=31533954" + }, + { + "expires": "Sun, 03 Nov 2013 12:58:01 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 61, + "wire": "88768c86b19272ad78fe8e92b015c36c96df3dbf4a01a535112a0801128166e34cdc6c0a62d1bff40f0d8365c781588ca47e561cc58190b6c89e135f6496dd6d5f4a0195349fba8200595022b8cbf702153168dfe9c5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 04 Oct 2012 13:43:50 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3680" + }, + { + "cache-control": "max-age=31532824" + }, + { + "expires": "Sun, 03 Nov 2013 12:39:11 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 62, + "wire": "88c16c96dc34fd281714cb6d4a080112806ae099b8dbaa62d1bff70f0d83644e33588ca47e561cc580200be17dc73f6496c361be940054d03b141002ca8115c65eb81654c5a37fecc8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Sat, 16 Jun 2012 04:23:57 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3263" + }, + { + "cache-control": "max-age=10191966" + }, + { + "expires": "Fri, 01 Mar 2013 12:38:13 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 63, + "wire": "88c46c96df697e94034a6e2d6a0801128166e34e5c032a62d1bffa0f0d83134e3f588ca47e561cc5804e01c75a7c5f6496dd6d5f4a002a6e2d6a080165403971905c0bea62d1bfefcb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Tue, 04 Sep 2012 13:46:03 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2469" + }, + { + "cache-control": "max-age=26067492" + }, + { + "expires": "Sun, 01 Sep 2013 06:30:19 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 64, + "wire": "88c7f36c96dc34fd281754be522820042a05db816ae34d298b46ff5f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ec938ec4153070df8567bf0f0d8313620f588ca47e561cc5802fb4fb8e059f6496d07abe940baa65b6a50400b2a01bb816ee34053168dff3cf7b8b84842d695b05443c86aa6f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Sat, 17 Dec 2011 17:14:44 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "2521" + }, + { + "cache-control": "max-age=19496613" + }, + { + "expires": "Mon, 17 Jun 2013 05:15:40 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 65, + "wire": "88ccf86c96df3dbf4a05b52f948a08010a8005c65bb811298b46ffda0f0d837c2cb3588ca47e561cc5802f89c65e03ff6496c361be940b4a65b6a50400b2a0457196ee32e298b46ff7d3c1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 15 Dec 2011 00:35:12 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-length": "9133" + }, + { + "cache-control": "max-age=19263809" + }, + { + "expires": "Fri, 14 Jun 2013 12:35:36 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 66, + "wire": "88cf6c96d07abe9413ea6a225410022502d5c086e36ca98b46ff5f88352398ac74acb37f0f0d8365d703588ca47e561cc58190b6cb4d09af6496dd6d5f4a0195349fba820059502cdc03771b0a98b46f6196dc34fd280654d27eea0801128166e322b80754c5a37fd8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 29 Oct 2012 14:11:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3761" + }, + { + "cache-control": "max-age=31534424" + }, + { + "expires": "Sun, 03 Nov 2013 13:05:51 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 67, + "wire": "88d46c96dd6d5f4a09953716b50400894002e05cb811298b46ffc20f0d8313edb9588ca47e561cc5804eb6269e6dff6496e4593e940bca6e2d6a0801654033702fdc69c53168dfc1db", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Sun, 23 Sep 2012 00:16:12 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2956" + }, + { + "cache-control": "max-age=27524859" + }, + { + "expires": "Wed, 18 Sep 2013 03:19:46 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 68, + "wire": "88d76c96d07abe9413ea6a225410022502d5c0b3700f298b46ffc50f0d83109973588ca47e561cc58190840db8d07f6496df697e9413ea6a22541002ca8166e36fdc13ca62d1bfc4de", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 29 Oct 2012 14:13:08 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2236" + }, + { + "cache-control": "max-age=31105641" + }, + { + "expires": "Tue, 29 Oct 2013 13:59:28 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 69, + "wire": "88da6c96d07abe9403aa681fa504008940337196ae05a53168dfc80f0d830b6007588ba47e561cc581a71b740d0b6496df3dbf4a09d52f948a080112810dc03f704fa98b46ffc7e1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 07 May 2012 03:34:14 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1500" + }, + { + "cache-control": "max-age=4657042" + }, + { + "expires": "Thu, 27 Dec 2012 11:09:29 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 70, + "wire": "88dd6c96e4593e940baa6a2254100225041b8d39700e298b46ffcb0f0d8313627b588ca47e561cc5804f38fbadb8ff6496df697e940054d444a820059502edc03571b714c5a37fcae4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Wed, 17 Oct 2012 21:46:06 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2528" + }, + { + "cache-control": "max-age=28697569" + }, + { + "expires": "Tue, 01 Oct 2013 17:04:56 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 71, + "wire": "88e06c96df697e940094d444a820044a083704edc134a62d1bffce0f0d830bec83588ca47e561cc5804f3c1134fbdf6496df3dbf4a019535112a0801654006e001704da98b46ffcde7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Tue, 02 Oct 2012 21:27:24 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1930" + }, + { + "cache-control": "max-age=28812498" + }, + { + "expires": "Thu, 03 Oct 2013 01:00:25 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 72, + "wire": "88e36c96dc34fd282029a8895040089400ae36edc13ca62d1bffd10f0d830ba217588ca47e561cc581903cf3ed01cf6496dd6d5f4a09d535112a0801654006e36ddc65953168dfd0ea", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Sat, 20 Oct 2012 02:57:28 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1722" + }, + { + "cache-control": "max-age=30889406" + }, + { + "expires": "Sun, 27 Oct 2013 01:55:33 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 73, + "wire": "88e66c96df697e940baa65b68504008940b571905c0b2a62d1bfd40f0d8313ee07588ca47e561cc5802e34c85c087f6496dd6d5f4a044a681fa50400b2a05db8d8ae05e53168dfd3ed", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Tue, 17 Jul 2012 14:30:13 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2961" + }, + { + "cache-control": "max-age=16431611" + }, + { + "expires": "Sun, 12 May 2013 17:52:18 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 74, + "wire": "88e96c96e4593e94134a6a225410022502d5c0b5700d298b46ffd70f0d83105c07588ca47e561cc581903ed36113ff6496dd6d5f4a09d535112a08016540bb704d5c0b8a62d1bfd6f0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Wed, 24 Oct 2012 14:14:04 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2160" + }, + { + "cache-control": "max-age=30945129" + }, + { + "expires": "Sun, 27 Oct 2013 17:24:16 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 75, + "wire": "88ec6c96df3dbf4a01a535112a080112816ae04371a654c5a37fda0f0d8313cd07588ca47e561cc5819008410baf7f6496dc34fd2817d4d444a820059500f5c0bd704da98b46ffd9f3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 04 Oct 2012 14:11:43 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2841" + }, + { + "cache-control": "max-age=30221178" + }, + { + "expires": "Sat, 19 Oct 2013 08:18:25 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 76, + "wire": "88ef6c96d07abe9403aa681fa504008940b371b7ee36fa98b46fdd0f0d830b6d07588ba47e561cc581d136fb2c8b6496dc34fd282714ca3a941002ca816ae00171b7d4c5a37fdcf6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 07 May 2012 13:59:59 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1541" + }, + { + "cache-control": "max-age=7259332" + }, + { + "expires": "Sat, 26 Jan 2013 14:00:59 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 77, + "wire": "88f26c96e4593e94038a65b6a504008940b5700ddc0b4a62d1bfe00f0d831044cf588ca47e561cc5802265969b69ef6496df697e94138a681d8a080165403b71a76e36da98b46fdff9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Wed, 06 Jun 2012 14:05:14 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2123" + }, + { + "cache-control": "max-age=12334548" + }, + { + "expires": "Tue, 26 Mar 2013 07:47:55 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 78, + "wire": "88f56c96df3dbf4a01a535112a080112816ae083702f298b46ffe30f0d03393437588ca47e561cc5804e85971c65cf6496c361be940b2a6e2d6a08016540b7704fdc132a62d1bfe2408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 04 Oct 2012 14:21:18 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "947" + }, + { + "cache-control": "max-age=27136636" + }, + { + "expires": "Fri, 13 Sep 2013 15:29:23 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 79, + "wire": "88f96c96df3dbf4a01a535112a080112816ae34ddc0b6a62d1bfe70f0d830bcd39588ca47e561cc5804fbc1640107f6496d07abe940b4a6a22541002ca816ae36ddc65d53168dfe6c1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 04 Oct 2012 14:45:15 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1846" + }, + { + "cache-control": "max-age=29813010" + }, + { + "expires": "Mon, 14 Oct 2013 14:55:37 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 80, + "wire": "88768c86b19272ad78fe8e92b015c36c96dc34fd282754d444a820044a05ab8176e34153168dffeb0f0d83105c73588ca47e561cc5819036071a71cf6496df697e941094d444a820059502ddc659b81694c5a37f6196dc34fd280654d27eea0801128166e322b80794c5a37fc6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Sat, 27 Oct 2012 14:17:41 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2166" + }, + { + "cache-control": "max-age=30506466" + }, + { + "expires": "Tue, 22 Oct 2013 15:33:14 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:08 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 81, + "wire": "88c26c96df3dbf4a01a535112a0801128166e34f5c6dd53168dfef0f0d8365f71a588ca47e561cc5819085c0b6ebff6496e4593e94640a6a22541002ca806ee321b8d3aa62d1bfc1c9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 04 Oct 2012 13:48:57 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3964" + }, + { + "cache-control": "max-age=31161579" + }, + { + "expires": "Wed, 30 Oct 2013 05:31:47 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:08 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 82, + "wire": "88c56c96df3dbf4a01a535112a0801128166e34cdc644a62d1bff20f0d8369c6d9588ca47e561cc5819036db8e845f6496e4593e94132a6a22541002ca806ee320b8d014c5a37fc4cc", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 04 Oct 2012 13:43:32 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4653" + }, + { + "cache-control": "max-age=30556712" + }, + { + "expires": "Wed, 23 Oct 2013 05:30:40 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:08 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 83, + "wire": "88c85a839bd9ab6c96df697e940854dc5ad410022502f5c68571b0a98b46ff5f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ec938ec4153070df8567bf0f0d84642d3ae7588ca47e561cc5804e3eeb6d34d76496e4593e940854dc5ad41002ca817ae342b8d854c5a37ff6d17b8b84842d695b05443c86aa6f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 11 Sep 2012 18:42:51 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "31476" + }, + { + "cache-control": "max-age=26975444" + }, + { + "expires": "Wed, 11 Sep 2013 18:42:51 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 84, + "wire": "88cec36c96dc34fd281754be522820042a05db816ae34da98b46ffc20f0d840842f07fbf588ca47e561cc5802fb4fb8d89ef6496d07abe940baa65b6a50400b2a01bb816ae05b53168dffad5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Sat, 17 Dec 2011 17:14:45 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "11181" + }, + { + "vary": "Accept-Encoding" + }, + { + "cache-control": "max-age=19496528" + }, + { + "expires": "Mon, 17 Jun 2013 05:14:15 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 85, + "wire": "88d16c96df3dbf4a01a535112a080112816ae05fb8cb2a62d1bf5f88352398ac74acb37f0f0d836db781588ca47e561cc58190b6cb6e381f6496dd6d5f4a0195349fba820059502cdc139704f298b46f6196dc34fd280654d27eea0801128166e322b80754c5a37fda", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 04 Oct 2012 14:19:33 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5580" + }, + { + "cache-control": "max-age=31535661" + }, + { + "expires": "Sun, 03 Nov 2013 13:26:28 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 86, + "wire": "88d64085b283cc693fa4adaaeb9e4a3c11566fbfde8f94ceabb8631c8abb85d0b4b059191c75e1caf0160790b41f5886a8eb10649cbf4003703370c7bdae0fe6f70da3521bfa06a5fc1c46a6bdd09d4d7baf9d4d5c36a97786e53869c8a6be1b54c9a77a97f0685376f854d7b70297b568534c3c54d5bef29a756452feed6a5ed5b7f9cc0f0d03393033d56401300f28ff1ec7be00b2d85f69f6c416de02a01042d89f540d09e75e75c540e09c0b2d36a819085b13ca81a13ce3c26550382700d34caa064216c4eaa0684f38f002a81c10197c0f2a008596c2fb4fb62744e3ea804fbc1540d2c1540e015032fbc0540d2c1540e015032fbafaa06960aa0700a81971e795034b0550380540c89b6d5034b0550380fb52f9e919aa82919aa5cb18fd589a5721e9fb5358d33c0c589a7fcf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9vl*th%7Fbad%7F714-13ac678af80-0x141" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "903" + }, + { + "date": "Sat, 03 Nov 2012 13:32:08 GMT" + }, + { + "expires": "0" + }, + { + "set-cookie": "HT=1351949521580%0211529%04287876%06261345%0311528%04286823%06260443%0311527%04286801%06203908%011351949527269%02981%04-1%060%03980%04-1%060%03979%04-1%060%03688%04-1%060%03255%04-1%060; Domain=main.ebayrtm.com; Path=/rtm" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 87, + "wire": "886196dc34fd280654d27eea0801128166e322b807d4c5a37f6496d07abe940baa5f291410022502e5c6817197d4c5a37f6c96e4593e940814cb6d4a08007d40b971b7ae36e298b46f5f87352398ac5754df52848fd24a8f0f1391fe5e04b1b8f95d65c71a2321b8e4a5fe7f768dd06258741e54ad9326e61c5c1f0f0d83085c0f588ba47e561cc581979e780007", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:09 GMT" + }, + { + "expires": "Mon, 17 Dec 2012 16:40:39 GMT" + }, + { + "last-modified": "Wed, 10 Jun 2009 16:58:56 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80fb69e73664c31:6fe\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "1161" + }, + { + "cache-control": "max-age=3888000" + } + ] + }, + { + "seqno": 88, + "wire": "88c46496e4593e9413ca693f750400894037700e5c132a62d1bf6c96dc34fd281714d03f4a08007d4006e05cb800298b46ff5f87352398ac4c697fc3c2c10f0d83085e070f1390fe40d3ce3524a46646c8f86e37187f9f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:09 GMT" + }, + { + "expires": "Wed, 28 Nov 2012 05:06:23 GMT" + }, + { + "last-modified": "Sat, 16 May 2009 01:16:00 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "1180" + }, + { + "etag": "\"04864dfc3d5c91:5b1\"" + } + ] + }, + { + "seqno": 89, + "wire": "880f0d023636be6c96c361be940bea65b6a504003ea05db8d82e32253168dfc40f138ffe40e351bce81c94247c371c785fcfc3c86496dc34fd282754d444a820044a01eb827ee34053168dffea", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "66" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Fri, 19 Jun 2009 17:50:32 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"064b8706f1c91:682\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "date": "Sat, 03 Nov 2012 13:32:09 GMT" + }, + { + "expires": "Sat, 27 Oct 2012 08:29:40 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 90, + "wire": "88c96496df3dbf4a05952f948a080112817ee32ddc69c53168df6c96df3dbf4a05d5340ec50400854106e01fb800a98b46ffc8c7c6c50f0d840b2e859f0f138ffe4118e4688124ae11e0dc71e17f3f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:09 GMT" + }, + { + "expires": "Thu, 13 Dec 2012 19:35:46 GMT" + }, + { + "last-modified": "Thu, 17 Mar 2011 21:09:01 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "13713" + }, + { + "etag": "\"0bad4c1cf6c81:682\"" + } + ] + }, + { + "seqno": 91, + "wire": "88e8dd6c96df697e940b4a612c6a080112807ae00171a754c5a37fdc0f0d846d903ef7588ca47e561cc5804d36e01f08bf6496e4593e940b4a436cca0801654102e0017197d4c5a37fd3efdb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 14 Feb 2012 08:00:47 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "53098" + }, + { + "cache-control": "max-age=24560912" + }, + { + "expires": "Wed, 14 Aug 2013 20:00:39 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 92, + "wire": "88ebe06c96dd6d5f4a09e535112a0801128266e34e5c0b6a62d1bfdf0f0d8413a203ff588ca47e561cc5819081b69a69ef6496d07abe9413ca6a22541002ca8266e34e5c0b6a62d1bfd6f2de", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Sun, 28 Oct 2012 23:46:15 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "27209" + }, + { + "cache-control": "max-age=31054448" + }, + { + "expires": "Mon, 28 Oct 2013 23:46:15 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 93, + "wire": "887689bf7b3e65a193777b3f5f87497ca589d34d1f0f0d03333937e56196dc34fd280654d27eea0801128166e322b810298b46ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "text/html" + }, + { + "content-length": "397" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:32:10 GMT" + } + ] + }, + { + "seqno": 94, + "wire": "88be6496dc34fd2816d4be522820044a01fb8db9704f298b46ff6c96dc34fd28171486d9941000ca8205c685704ea98b46ffcdd2d1d00f0d023439", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:10 GMT" + }, + { + "expires": "Sat, 15 Dec 2012 09:56:28 GMT" + }, + { + "last-modified": "Sat, 16 Aug 2003 20:42:27 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "49" + } + ] + }, + { + "seqno": 95, + "wire": "88c06c96d07abe9403ca681d8a080102817ee322b8cb6a62d1bfced3d20f0d03313436", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:10 GMT" + }, + { + "last-modified": "Mon, 08 Mar 2010 19:32:35 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "146" + } + ] + }, + { + "seqno": 96, + "wire": "88f46c96d07abe941094d444a820044a0817197ae36da98b46ffe00f0d830be17b588ca47e561cc5819001b0b2107f6496df3dbf4a05d535112a080165403f700edc0baa62d1bfdffb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 22 Oct 2012 20:38:55 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1918" + }, + { + "cache-control": "max-age=30051310" + }, + { + "expires": "Thu, 17 Oct 2013 09:07:17 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 97, + "wire": "88d16c96df3dbf4a002a693f7504008940bd7000b8cb2a62d1bf6196c361be940094d27eea080112817ae045700ea98b46ff6496dc34fd280654d27eea080112817ae045700ea98b46ff4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb768344b2970f0d84138f041f408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f558471f700cf5890aed8e8313e94a47e561cc581e71a003f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 01 Nov 2012 18:00:33 GMT" + }, + { + "date": "Fri, 02 Nov 2012 18:12:07 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 18:12:07 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "26810" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "69603" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 98, + "wire": "88768c86b19272ad78fe8e92b015c36c96d07abe941094d444a820044a099b806ae044a62d1bff5f89352398ac7958c43d5f0f0d83085b076196dc34fd280654d27eea0801128166e322b810a98b46ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 22 Oct 2012 23:04:12 GMT" + }, + { + "content-type": "image/x-icon" + }, + { + "content-length": "1150" + }, + { + "date": "Sat, 03 Nov 2012 13:32:11 GMT" + } + ] + }, + { + "seqno": 99, + "wire": "886196dc34fd280654d27eea0801128166e322b811298b46ff6496d07abe94138a693f750400894002e09fb82694c5a37f6c96c361be940bca681d8a08006d4133700cdc6da53168dfe0e5e4e30f0d8213800f1390fe40d3ce3524a46646c8f86e37187f9f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:12 GMT" + }, + { + "expires": "Mon, 26 Nov 2012 00:29:24 GMT" + }, + { + "last-modified": "Fri, 18 Mar 2005 23:03:54 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "260" + }, + { + "etag": "\"04864dfc3d5c91:5b1\"" + } + ] + }, + { + "seqno": 100, + "wire": "88c06496df697e940854be522820044a08171a6ee34f298b46ff6c96c361be9413aa436cca0801028005c10ae36ca98b46ffe2e70f1390fe5e005e66491c7a31c8490371d103f9e6e50f0d03333636", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:12 GMT" + }, + { + "expires": "Tue, 11 Dec 2012 20:45:48 GMT" + }, + { + "last-modified": "Fri, 27 Aug 2010 00:22:53 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80183dd68badcd1:720\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "366" + } + ] + }, + { + "seqno": 101, + "wire": "88c26c96c361be940bca681d8a08006d4133700d5c65f53168dfe3e8e70f0d820ba20f1390fe5e015928de23e38c9206e38cbffcff6496df697e94038a693f7504008940b971b76e09e53168df", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:12 GMT" + }, + { + "last-modified": "Fri, 18 Mar 2005 23:04:39 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "172" + }, + { + "etag": "\"80e3ea8c9abcd1:639\"" + }, + { + "expires": "Tue, 06 Nov 2012 16:57:28 GMT" + } + ] + }, + { + "seqno": 102, + "wire": "88c80f28bba2d275f4fc017db7de7c1f6a5f3d2335502e58c7e9721e9fb53079acd615106f9edfa50025b49fbac2005d502cdc645702253168dff6a6b1a678185885aec3771a4b4085aec1cd48ff86a8eb10649cbf5a839bd9ab5f91497ca589d34d1f649c7620a98386fc2b3d0f0d847c0f89bfc9cb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lucky9=1959890; Domain=.ebay.com; Expires=Thu, 02-Nov-2017 13:32:12 GMT; Path=/" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-length": "90925" + }, + { + "date": "Sat, 03 Nov 2012 13:32:11 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 23:04:12 GMT" + } + ] + }, + { + "seqno": 103, + "wire": "88c86496df3dbf4a05952f948a080112817ee00171a0298b46ff6c96df3dbf4a01f53716b504008140bb7190dc640a62d1bfce54012aeefc0f0d84085b035f408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:12 GMT" + }, + { + "expires": "Thu, 13 Dec 2012 19:00:40 GMT" + }, + { + "last-modified": "Thu, 09 Sep 2010 17:31:30 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "11504" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 104, + "wire": "88ccc1c0d0bfef5f88352398ac74acb37f0f0d84085b035fbf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:12 GMT" + }, + { + "expires": "Thu, 13 Dec 2012 19:00:40 GMT" + }, + { + "last-modified": "Thu, 09 Sep 2010 17:31:30 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "11504" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 105, + "wire": "88d16c96df3dbf4a05e535112a0801128215c69cb8d3ea62d1bfbf0f0d8365f705588ca47e561cc5804f81c7997def6496dd6d5f4a01c535112a0801654002e01bb8c814c5a37fd0c2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 18 Oct 2012 22:46:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3962" + }, + { + "cache-control": "max-age=29068398" + }, + { + "expires": "Sun, 06 Oct 2013 00:05:30 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:12 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 106, + "wire": "88d46c96e4593e94642a6a225410022502edc6d9b8d814c5a37fc20f0d8369c741588ca47e561cc5819089d700dbdf6496df3dbf4a321535112a08016540b3702fdc6c0a62d1bfd3c5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Wed, 31 Oct 2012 17:53:50 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4670" + }, + { + "cache-control": "max-age=31276058" + }, + { + "expires": "Thu, 31 Oct 2013 13:19:50 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:12 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 107, + "wire": "88d76c96df3dbf4a042a6a2254100225001b8d8ae36da98b46ffc50f0d836dc75f588ca47e561cc5804f3ccb40685f6496df3dbf4a019535112a080165403971b7ee32d298b46fd6c8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 11 Oct 2012 01:52:55 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5679" + }, + { + "cache-control": "max-age=28834042" + }, + { + "expires": "Thu, 03 Oct 2013 06:59:34 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:12 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 108, + "wire": "88da6c96e4593e94136a65b6850400894102e360b8d054c5a37fc80f0d8365f13f588ca47e561cc5802ebeebcf81af6497df3dbf4a3205340fd2820059502ddc681719714c5a37ffd9cb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Wed, 25 Jul 2012 20:50:41 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3929" + }, + { + "cache-control": "max-age=17978904" + }, + { + "expires": "Thu, 30 May 2013 15:40:36 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:12 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 109, + "wire": "88dd6c96d07abe94038a436cca0801128072e059b82794c5a37fcb0f0d83134eb3588ca47e561cc58042032e3aebdf6496df697e940b8a65b6850400b2a05ab8d86e36053168dfdcce", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 06 Aug 2012 06:13:28 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2473" + }, + { + "cache-control": "max-age=22036778" + }, + { + "expires": "Tue, 16 Jul 2013 14:51:50 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:12 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 110, + "wire": "88e06c96d07abe9413aa6e2d6a080102807ee01db82694c5a37f5f90497ca582211f649c7620a98386fc2b3d5b842d4b70ddd60f0d83702e8b7b8b84842d695b05443c86aa6f5892aec3771a4bf4a523f2b0e62c0c85b65c00016496dd6d5f4a0195349fba820059502cdc645702253168dfe2d4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 27 Sep 2010 09:07:24 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "6172" + }, + { + "vary": "Accept-Encoding" + }, + { + "cache-control": "private, max-age=31536000" + }, + { + "expires": "Sun, 03 Nov 2013 13:32:12 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:12 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 111, + "wire": "88e6d96c96c361be940b8a681d8a0801128005c681704ca98b46ff5f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ec938ec4153070df8567bf0f0d846c4fb60f588ca47e561cc5804e882279f17f6496dc34fd281694dc5ad41002ca8115c681704d298b46ffe6d8c4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 16 Mar 2012 00:40:23 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "52950" + }, + { + "cache-control": "max-age=27212892" + }, + { + "expires": "Sat, 14 Sep 2013 12:40:24 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:12 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 112, + "wire": "88ea6c96dd6d5f4a09d5340fd2820044a045704e5c0bca62d1bfd80f0d83702e33588ca47e561cc5802ebed05b65bf6497df3dbf4a3205340fd2820059500ddc0bb71a754c5a37ffe9db", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Sun, 27 May 2012 12:26:18 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6163" + }, + { + "cache-control": "max-age=17941535" + }, + { + "expires": "Thu, 30 May 2013 05:17:47 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:12 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 113, + "wire": "88ed6c96d07abe940b2a436cca0801128072e362b8cb6a62d1bfdb0f0d8369b139588ca47e561cc5804d34fbcf881f6496e4593e940b4a436cca080165400ae34edc65953168df6196dc34fd280654d27eea0801128166e322b81654c5a37fdf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 13 Aug 2012 06:52:35 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4526" + }, + { + "cache-control": "max-age=24498920" + }, + { + "expires": "Wed, 14 Aug 2013 02:47:33 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:13 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 114, + "wire": "88f1cec7cccbe40f0d8469e7c4d75892aed8e8313e94a47e561cc58190b6cb80003fcaeee0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 27 Sep 2010 09:07:24 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "48924" + }, + { + "cache-control": "public, max-age=31536000" + }, + { + "expires": "Sun, 03 Nov 2013 13:32:12 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:12 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 115, + "wire": "886196dc34fd280654d27eea0801128166e322b81694c5a37f6496d07abe940baa5f291410022502e5c6817197d4c5a37f6c96e4593e940bea6a2254100215000b8d3d702fa98b46ff5f87352398ac5754df52848fd24a8f0f1391fe5e04b1b8f95d65c71a2321b8e4a5fe7f768dd06258741e54ad9326e61c5c1f0f0d03323735588ba47e561cc581979e780007", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:14 GMT" + }, + { + "expires": "Mon, 17 Dec 2012 16:40:39 GMT" + }, + { + "last-modified": "Wed, 19 Oct 2011 00:48:19 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80fb69e73664c31:6fe\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "275" + }, + { + "cache-control": "max-age=3888000" + } + ] + }, + { + "seqno": 116, + "wire": "880f0d83085a6fc16c96df697e940b6a612c6a08010a8176e32e5c0854c5a37fc10f1391fe5e046ec8391b65c24848c371e6dafe7fc0c56497dd6d5f4a09b5349fba820044a099b8d3971b6d4c5a37ffe9c0", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "1145" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Tue, 15 Feb 2011 17:36:11 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80b7dad536cdcb1:854\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "date": "Sat, 03 Nov 2012 13:32:14 GMT" + }, + { + "expires": "Sun, 25 Nov 2012 23:46:55 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=3888000" + } + ] + }, + { + "seqno": 117, + "wire": "88c66496dd6d5f4a01a5349fba820044a01ab8dbf704da98b46f6c96e4593e940094c258d410021502fdc699b81754c5a37f5f87352398ac4c697fc50f1390fe5e005e66491c7a31c8490371d103f9c4c30f0d8365b69c", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:14 GMT" + }, + { + "expires": "Sun, 04 Nov 2012 04:59:25 GMT" + }, + { + "last-modified": "Wed, 02 Feb 2011 19:43:17 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80183dd68badcd1:720\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "3546" + } + ] + }, + { + "seqno": 118, + "wire": "88c96496df3dbf4a09d53716b50400894133702ddc680a62d1bf6c96c361be941054ca3a94100215040b8d8ae36253168dffc0c7c6c50f0d837196850f1390fe4031baf0a31c91be48c371c785fcffee", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:14 GMT" + }, + { + "expires": "Thu, 27 Sep 2012 23:15:40 GMT" + }, + { + "last-modified": "Fri, 21 Jan 2011 20:52:52 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "6342" + }, + { + "etag": "\"0aa782badb9cb1:682\"" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 119, + "wire": "88cb6c96df3dbf4a01f53716b5040081403371a05c1014c5a37fc9c8c70f0d84081e7dcf0f1390fe5e015928de23e38c9206e38cbffcff6496dd6d5f4a05c52f948a080112806ee34d5c65e53168dfc7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:14 GMT" + }, + { + "last-modified": "Thu, 09 Sep 2010 03:40:20 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "10896" + }, + { + "etag": "\"80e3ea8c9abcd1:639\"" + }, + { + "expires": "Sun, 16 Dec 2012 05:44:38 GMT" + }, + { + "cache-control": "max-age=3888000" + } + ] + }, + { + "seqno": 120, + "wire": "88cd6497dd6d5f4a09b5349fba820044a099b8d3971b754c5a37ff6c96c361be941094cb6d4a0801128215c03f704153168dffcccbcac90f0d846df742ff0f1390fe40d3ce3524a46646c8f86e37187f9f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:14 GMT" + }, + { + "expires": "Sun, 25 Nov 2012 23:46:57 GMT" + }, + { + "last-modified": "Fri, 22 Jun 2012 22:09:21 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "59719" + }, + { + "etag": "\"04864dfc3d5c91:5b1\"" + } + ] + }, + { + "seqno": 121, + "wire": "88cfbecccbca0f0d846df742ffbfc9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:14 GMT" + }, + { + "last-modified": "Fri, 22 Jun 2012 22:09:21 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "59719" + }, + { + "expires": "Sun, 25 Nov 2012 23:46:57 GMT" + }, + { + "cache-control": "max-age=3888000" + } + ] + }, + { + "seqno": 122, + "wire": "88768c86b19272ad78fe8e92b015c34085b283cc693faaadaa9570165bd25b64a3c11566fbfdd3f29438eaee0a46d566f2ace07560b23238ebc47da65607908daf588caec3771a4bf4a547588324e5fb5f87497ca58e883d5f798624f6d5d4b27fd4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4n%60rujfudlwc%3D9vt*ts67.62d5%3C%3E7-13ac678c943-0x1a4" + }, + { + "cache-control": "private, no-cache" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/json" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 13:32:14 GMT" + } + ] + }, + { + "seqno": 123, + "wire": "88c20f28bca2d275f4fc017db7de7c1f6a5f3d2335502e58c7e9721e9fb53079acd615106f9edfa50025b49fbac2005d502cdc645702e298b46ffb5358d33c0c7f5885aec3771a4b4085aec1cd48ff86a8eb10649cbf5a839bd9ab5f95497ca58e83ee3412c3569fb24e3b1054c1c37e159e0f0d84780069cf6196dc34fd280654d27eea0801128166e322b816d4c5a37f6c96d07abe941094d444a820044a099b806ae044a62d1bff40884d83a903224c7abfcab7aaf67fb700ec7ffdfffa8fada4a64c922984d5486aa6d761e4b489eed7d6da0f364914adaae937855c0744c6faace1b7aaf62a2bae01d8d57702ae010b059191c75e24627560798ddf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lucky9=1959890; Domain=.ebay.com; Expires=Thu, 02-Nov-2017 13:32:16 GMT; Path=/" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/javascript;charset=UTF-8" + }, + { + "content-length": "80046" + }, + { + "date": "Sat, 03 Nov 2012 13:32:15 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 23:04:12 GMT" + }, + { + "transaction": "uk.r+607b~`s,RcmdId FindingProductv4,RlogId p4pmiw%60jtb9%3Fuk.r%2B607b%7E%60s-13ac678cb27-0xb7" + } + ] + }, + { + "seqno": 124, + "wire": "886196dc34fd280654d27eea0801128166e322b81714c5a37f6496d07abe940baa5f291410022502cdc69cb8db4a62d1bf6c96df3dbf4a01f53716b504008140bb7190dc640a62d1bfcc54012ad95f88352398ac74acb37f0f0d03373339408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:16 GMT" + }, + { + "expires": "Mon, 17 Dec 2012 13:46:54 GMT" + }, + { + "last-modified": "Thu, 09 Sep 2010 17:31:30 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "739" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 125, + "wire": "88c36496df3dbf4a084a693f7504008940b5702fdc682a62d1bf6c96dc34fd28265486bb1410021500fdc65db8d014c5a37fd1c2ddc10f0d830b8d3fc0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:16 GMT" + }, + { + "expires": "Thu, 22 Nov 2012 14:19:41 GMT" + }, + { + "last-modified": "Sat, 23 Apr 2011 09:37:40 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1649" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 126, + "wire": "88c56496dd6d5f4a01a5349fba820044a01cb8105c13aa62d1bf6c96c361be940bca681d8a08006d41337001b80694c5a37fdae1e0df0f0d033133340f138ffe40ebcd89b214442361b8dbce7f3f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:16 GMT" + }, + { + "expires": "Sun, 04 Nov 2012 06:10:27 GMT" + }, + { + "last-modified": "Fri, 18 Mar 2005 23:01:04 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "134" + }, + { + "etag": "\"078525ce2cc51:586\"" + } + ] + }, + { + "seqno": 127, + "wire": "88c76496c361be940b4a5f291410022500ddc03d702ca98b46ff6c96c361be941054d444a82001b502f5c69db8d014c5a37fdce30f1391fe5e04b1b8f95d65c71a2321b8e4a5fe7fe20f0d821321e1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:16 GMT" + }, + { + "expires": "Fri, 14 Dec 2012 05:08:13 GMT" + }, + { + "last-modified": "Fri, 21 Oct 2005 18:47:40 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80fb69e73664c31:6fe\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "231" + }, + { + "cache-control": "max-age=3888000" + } + ] + }, + { + "seqno": 128, + "wire": "88c96496dd6d5f4a01f52f948a0801128072e362b81794c5a37f6c96df697e94038a6e2d6a08006d40bf71976e34e298b46fdee50f1390fe5e005e66491c7a31c8490371d103f9e4e30f0d03363433", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:16 GMT" + }, + { + "expires": "Sun, 09 Dec 2012 06:52:18 GMT" + }, + { + "last-modified": "Tue, 06 Sep 2005 19:37:46 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80183dd68badcd1:720\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "643" + } + ] + }, + { + "seqno": 129, + "wire": "88cb6496c361be94138a6a225410022500d5c699b8c854c5a37f6c96df3dbf4a01c535112a08006d4106e09fb81694c5a37fe0e7e6e50f0d033336390f1390fe4031baf0a31c91be48c371c785fcffc8", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:16 GMT" + }, + { + "expires": "Fri, 26 Oct 2012 04:43:31 GMT" + }, + { + "last-modified": "Thu, 06 Oct 2005 21:29:14 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "369" + }, + { + "etag": "\"0aa782badb9cb1:682\"" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 130, + "wire": "880f0d03313939e06c96c361be941054d444a82001b502f5c69db8d094c5a37fe80f1391fe5e046ec8391b65c24848c371e6dafe7fe7cee4c9e6", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "199" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Fri, 21 Oct 2005 18:47:42 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80b7dad536cdcb1:854\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "date": "Sat, 03 Nov 2012 13:32:16 GMT" + }, + { + "expires": "Sun, 25 Nov 2012 23:46:55 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=3888000" + } + ] + }, + { + "seqno": 131, + "wire": "88ce6c96e4593e94034a681d8a08007d40337022b8c814c5a37feae9e80f0d83132f3d0f1390fe5e015928de23e38c9206e38cbffcffdee7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:16 GMT" + }, + { + "last-modified": "Wed, 04 Mar 2009 03:12:30 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "2388" + }, + { + "etag": "\"80e3ea8c9abcd1:639\"" + }, + { + "expires": "Sun, 16 Dec 2012 05:44:38 GMT" + }, + { + "cache-control": "max-age=3888000" + } + ] + }, + { + "seqno": 132, + "wire": "88cf6496d07abe940bea693f75040089410ae08171a714c5a37fcedce8cc0f0d830ba167", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:16 GMT" + }, + { + "expires": "Mon, 19 Nov 2012 22:20:46 GMT" + }, + { + "last-modified": "Thu, 09 Sep 2010 17:31:30 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1713" + } + ] + }, + { + "seqno": 133, + "wire": "88d06496dd6d5f4a042a693f75040089403971a05c1054c5a37f6c96df3dbf4a01f53716b504008140bb71905c65e53168dfdecfeace0f0d830b2f3fcd", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:16 GMT" + }, + { + "expires": "Sun, 11 Nov 2012 06:40:21 GMT" + }, + { + "last-modified": "Thu, 09 Sep 2010 17:30:38 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1389" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 134, + "wire": "88d26496d07abe94138a693f7504008940357190dc684a62d1bf6c96c361be940bca681d8a08006d4133700d5c65a53168dfe7ee0f1391fe5e04b1b8f95d65c71a2321b8e4a5fe7fed0f0d820b41ec", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:16 GMT" + }, + { + "expires": "Mon, 26 Nov 2012 04:31:42 GMT" + }, + { + "last-modified": "Fri, 18 Mar 2005 23:04:34 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80fb69e73664c31:6fe\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "141" + }, + { + "cache-control": "max-age=3888000" + } + ] + }, + { + "seqno": 135, + "wire": "88d46497dd6d5f4a09b5349fba820044a099b8d3971b714c5a37ff6c96c361be940bca681d8a08006d4133700ddc0854c5a37fe9f0efee0f0d033133360f138ffe5e00e47a32ca51108d86e3c56bf9d1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:16 GMT" + }, + { + "expires": "Sun, 25 Nov 2012 23:46:56 GMT" + }, + { + "last-modified": "Fri, 18 Mar 2005 23:05:11 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "136" + }, + { + "etag": "\"80ad8befe2cc51:8e4\"" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 136, + "wire": "88d66496df3dbf4a09f5349fba820044a05cb8cbf704153168df6c96df697e94009486d99410021500edc6dbb807d4c5a37fe4f0d40f0d8371c6c3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:16 GMT" + }, + { + "expires": "Thu, 29 Nov 2012 16:39:21 GMT" + }, + { + "last-modified": "Tue, 02 Aug 2011 07:55:09 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6651" + } + ] + }, + { + "seqno": 137, + "wire": "88d86496df3dbf4a01c52f948a080112807ee342b810a98b46ff6c96d07abe9413ca681d8a08010a816ae36ddc6df53168dfe6d7f2d60f0d840b2f09bfd5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:16 GMT" + }, + { + "expires": "Thu, 06 Dec 2012 09:42:11 GMT" + }, + { + "last-modified": "Mon, 28 Mar 2011 14:55:59 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "13825" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 138, + "wire": "886196dc34fd280654d27eea0801128166e322b820a98b46ff6c96dc34fd28171486d9941000ca8205c685704da98b46ffeff6f50f0d023439eaf4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "last-modified": "Sat, 16 Aug 2003 20:42:25 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "49" + }, + { + "expires": "Sun, 25 Nov 2012 23:46:57 GMT" + }, + { + "cache-control": "max-age=3888000" + } + ] + }, + { + "seqno": 139, + "wire": "88e80f28baa2d275f4fc017db7de7c1f77cf48cd540b9631fa5c87a7ef079acd615106f9edfa50025b49fbac2005d502cdc645704153168dff7ac699e0614fe3e2e15f91497ca589d34d1f649c7620a98386fc2b3d0f0d84780069cfc0dfde7f29a44b96d04afa762747d5670dbd55700191d14aa8ae844ab808d60b23238ebc503e5581e4805b842d4b70dde7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lucky9=1959890;Domain=.ebay.com;Expires=Thu, 02-Nov-2017 13:32:21 GMT;Path=/ " + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-length": "80046" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 23:04:12 GMT" + }, + { + "transaction": "uk.r+607b~`s,RcmdId FindingProductv4,RlogId p4pmiw%60jtb9%3Fuk.r%2B607b%7E%60s-13ac678cb27-0xb7" + }, + { + "rlogid": "t6ulcpjqcj9%3Fuk%601d72f%2B12%60b-13ac678e09e-0xc0" + }, + { + "content-language": "en-US" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 140, + "wire": "88c26496dd6d5f4a05e5349fba820044a085704f5c034a62d1bf6c96df3dbf4a01a535112a0800754106e34d5c65f53168dff452848fd24a8f768dd06258741e54ad9326e61c5c1f588ba47e561cc581979e7800070f0d83642ebf0f138ffe40ebcd89b214442361b8dbce7f3f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "expires": "Sun, 18 Nov 2012 22:28:04 GMT" + }, + { + "last-modified": "Thu, 04 Oct 2007 21:44:39 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "3179" + }, + { + "etag": "\"078525ce2cc51:586\"" + } + ] + }, + { + "seqno": 141, + "wire": "88c76c96c361be9413aa436cca0801028005c10ae34f298b46fff8c1c00f0d033339316496dc34fd280654d27eea080112816ee059b821298b46ffc0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "last-modified": "Fri, 27 Aug 2010 00:22:48 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "391" + }, + { + "expires": "Sat, 03 Nov 2012 15:13:22 GMT" + }, + { + "cache-control": "max-age=3888000" + } + ] + }, + { + "seqno": 142, + "wire": "88c9ce5f87352398ac4c697fc3c20f0d03313336", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "last-modified": "Fri, 18 Mar 2005 23:05:11 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "136" + } + ] + }, + { + "seqno": 143, + "wire": "88ca6c96df3dbf4a01c53716b504003aa0017021b8d3aa62d1bfbfc4c30f0d03353432", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "last-modified": "Thu, 06 Sep 2007 00:11:47 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "542" + } + ] + }, + { + "seqno": 144, + "wire": "88cb6497dd6d5f4a09b5349fba820044a099b8d3971b6d4c5a37ff6c96df3dbf4a01c532db4282001c5000b8076e000a62d1bfc1c60f138ffe403185b08df00c0470371b28bf9fc5c40f0d023433", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "expires": "Sun, 25 Nov 2012 23:46:55 GMT" + }, + { + "last-modified": "Thu, 06 Jul 2006 00:07:00 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"0aa151a90a0c61:5e2\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "43" + } + ] + }, + { + "seqno": 145, + "wire": "886196dc34fd280654d27eea0801128166e322b821298b46ff6c96df697e94134a681fa5040085410ae32cdc682a62d1bfc3c8c70f0d830b2d87c4c6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + }, + { + "last-modified": "Tue, 24 May 2011 22:33:41 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "1351" + }, + { + "expires": "Sat, 03 Nov 2012 15:13:22 GMT" + }, + { + "cache-control": "max-age=3888000" + } + ] + }, + { + "seqno": 146, + "wire": "88cf6c96df3dbf4a01c53716b504003aa0017041b8c854c5a37fc4c90f1390fe5e04ae8c124a18e5011d0dc6e30ff3c80f0d8371979c", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "last-modified": "Thu, 06 Sep 2007 00:21:31 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80f7a0df1bf0c71:5b1\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "6386" + } + ] + }, + { + "seqno": 147, + "wire": "88768c86b19272ad78fe8e92b015c3f36c96df3dbf4a09b535112a0801128266e321b8d3aa62d1bf5f90497ca582211f649c7620a98386fc2b3d0f0d83644eb9588ca47e561cc581903afb4cb8e76496c361be94136a6a22541002ca8266e321b8d3aa62d1bfd5ed7b8b84842d695b05443c86aa6f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 25 Oct 2012 23:31:47 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-length": "3276" + }, + { + "cache-control": "max-age=30794366" + }, + { + "expires": "Fri, 25 Oct 2013 23:31:47 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 148, + "wire": "88c3f8c2c10f0d03363638c0bfd6eebe", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 25 Oct 2012 23:31:47 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-length": "668" + }, + { + "cache-control": "max-age=30794366" + }, + { + "expires": "Fri, 25 Oct 2013 23:31:47 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 149, + "wire": "88c3f86c96dc34fd281694cb6d0a0801128005c6c171a754c5a37fc20f0d023434588ca47e561cc58041782cb6073f6496dd6d5f4a05a532db428200595000b8d82e34ea98b46fd9f1c1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Sat, 14 Jul 2012 00:50:47 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-length": "44" + }, + { + "cache-control": "max-age=21813506" + }, + { + "expires": "Sun, 14 Jul 2013 00:50:47 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 150, + "wire": "88d9df6c96df697e940094c258d410020502fdc69ab81694c5a37fced3d2d10f0d840b2f05df", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "expires": "Sun, 25 Nov 2012 23:46:56 GMT" + }, + { + "last-modified": "Tue, 02 Feb 2010 19:44:14 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "13817" + } + ] + }, + { + "seqno": 151, + "wire": "88c76c96d07abe9413aa6e2d6a080102807ee01db82694c5a37fc65b842216bdfb5a839bd9ab0f0d83702e8bc55892aec3771a4bf4a523f2b0e62c0c85b65c00016496dd6d5f4a0195349fba820059502cdc645704153168dfdff7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 27 Sep 2010 09:07:24 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-language": "cs-CZ" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "6172" + }, + { + "vary": "Accept-Encoding" + }, + { + "cache-control": "private, max-age=31536000" + }, + { + "expires": "Sun, 03 Nov 2013 13:32:21 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 152, + "wire": "88ccc06c96c361be940094d27eea0801128066e09eb8c814c5a37fcb0f0d8371c703588ca47e561cc58190b4165971ff6496dc34fd280129a4fdd41002ca8066e09eb8c814c5a37fe2408721eaa8a4498f5788ea52d6b0e83772ffcb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 03:28:30 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-length": "6661" + }, + { + "cache-control": "max-age=31413369" + }, + { + "expires": "Sat, 02 Nov 2013 03:28:30 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 153, + "wire": "88d0c46c96c361be94038a65b6850400894006e01eb8d34a62d1bfcf0f0d03333531cc588ca47e561cc58041089965d7bf6496dc34fd280714cb6d0a0801654006e01eb8cbea62d1bfe6c1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 06 Jul 2012 01:08:44 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-length": "351" + }, + { + "vary": "Accept-Encoding" + }, + { + "cache-control": "max-age=21123378" + }, + { + "expires": "Sat, 06 Jul 2013 01:08:39 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 154, + "wire": "88d36c96d07abe941094d444a820044a019b8076e32153168dff5f88352398ac74acb37f0f0d8369b6d9588ca47e561cc5804fbce38dbee76496df697e940b6a6a22541002ca806ee34f5c6dd53168dfeac5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 22 Oct 2012 03:07:31 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4553" + }, + { + "cache-control": "max-age=29866596" + }, + { + "expires": "Tue, 15 Oct 2013 05:48:57 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 155, + "wire": "88d76c96dc34fd282129b8b5a820044a05fb8d86e32f298b46ffc10f0d8369c7da588ca47e561cc5804d32ebad805f6496d07abe94089486d9941002ca8176e01ab80654c5a37fedc8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Sat, 22 Sep 2012 19:51:38 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4694" + }, + { + "cache-control": "max-age=24377502" + }, + { + "expires": "Mon, 12 Aug 2013 17:04:03 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 156, + "wire": "88da6c96df697e94640a6a2254100225041b8d3d702da98b46ffc40f0d836d903b588ca47e561cc58190b4fbce819f6496dd6d5f4a0195349fba820059500cdc082e34d298b46ff0cb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Tue, 30 Oct 2012 21:48:15 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5307" + }, + { + "cache-control": "max-age=31498703" + }, + { + "expires": "Sun, 03 Nov 2013 03:10:44 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 157, + "wire": "88ddd16c96df3dbf4a09b535112a0801128266e322b806d4c5a37fdc0f0d8364016b588ca47e561cc581903afb4cb4cf6496c361be94136a6a22541002ca8266e321b82694c5a37ff3cedb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 25 Oct 2012 23:32:05 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-length": "3014" + }, + { + "cache-control": "max-age=30794343" + }, + { + "expires": "Fri, 25 Oct 2013 23:31:24 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 158, + "wire": "88e36496df697e940bca5f291410022502cdc645704253168dff6c96e4593e94642a6a225410022502edc6d9b8d054c5a37fe254012aedcc0f0d840b6fbc1fd1", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + }, + { + "expires": "Tue, 18 Dec 2012 13:32:22 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 17:53:41 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "15981" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 159, + "wire": "88e6f16c96c361be94136a65b6a50400814006e09eb82794c5a37f5f87352398ac5754dff1f0ef0f0d033537380f138ffe40471e7a371b0b448c371b79cfe7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + }, + { + "expires": "Sun, 18 Nov 2012 22:28:04 GMT" + }, + { + "last-modified": "Fri, 25 Jun 2010 01:28:28 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "578" + }, + { + "etag": "\"0c688b6514cb1:586\"" + } + ] + }, + { + "seqno": 160, + "wire": "88e5db5f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ec938ec4153070df8567bf5b84ad2b5ddbe2db0f0d837dc79e5892aed8e8313e94a47e561cc58190b6cb80003fda6196dc34fd280654d27eea0801128166e322b820a98b46ffd7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 27 Sep 2010 09:07:24 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-language": "pt-BR" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "9688" + }, + { + "cache-control": "public, max-age=31536000" + }, + { + "expires": "Sun, 03 Nov 2013 13:32:21 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:21 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 161, + "wire": "88ec6c96df697e941014dc5ad410021504cdc6dfb8d3aa62d1bff1f6f50f0d830baf030f1390fe5e015928de23e38c9206e38cbffcff6496d07abe94138a693f750400894002e019b8d054c5a37ff5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + }, + { + "last-modified": "Tue, 20 Sep 2011 23:59:47 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "1780" + }, + { + "etag": "\"80e3ea8c9abcd1:639\"" + }, + { + "expires": "Mon, 26 Nov 2012 00:03:41 GMT" + }, + { + "cache-control": "max-age=3888000" + } + ] + }, + { + "seqno": 162, + "wire": "88eb4085b283cc693fa3adaaeb9e4a3c11566fbf6aaee0f94aa279d6c1301dad60b2fbed35295a7e3581e42eb96c96df3dbf4a099521b66504008940bb704d5c644a62d1bf4003703370c7bdae0fe6f70da3521bfa06a5fc1c46a6bdd09d4d7baf9d4d5c36a97786e53869c8a6be1b54c9a77a97f0685376f854d7b70297b568534c3c54d5bef29a756452feed6a5ed5b7f95f86497ca582211f0f0d8379d10b588ca47e561cc5804e32fbed3ad76496df3dbf4a01b53716b50400b2a00571a66e32e298b46ff4df", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9u%7E*t%28750g07p-139944fe49b-0x176" + }, + { + "last-modified": "Thu, 23 Aug 2012 17:24:32 GMT" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "text/css" + }, + { + "content-length": "8722" + }, + { + "cache-control": "max-age=26399474" + }, + { + "expires": "Thu, 05 Sep 2013 02:43:36 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 163, + "wire": "88f46c96e4593e940894ca3a94100215040b8276e01f53168dffcb52848fd24a8f768dd06258741e54ad9326e61c5c1f0f0d033334326496d07abe94138a693f7504008940b97196ee05f53168df588ba47e561cc581979e780007", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + }, + { + "last-modified": "Wed, 12 Jan 2011 20:27:09 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "342" + }, + { + "expires": "Mon, 26 Nov 2012 16:35:19 GMT" + }, + { + "cache-control": "max-age=3888000" + } + ] + }, + { + "seqno": 164, + "wire": "88f96497dd6d5f4a09b5349fba820044a099b8d3971b6d4c5a37ff6c96e4593e940bea6a2254100215001b8176e34ea98b46ffd1c3c2c00f0d8371c6830f1390fe5e00e513e57e523d21081b8f15afe7e6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + }, + { + "expires": "Sun, 25 Nov 2012 23:46:55 GMT" + }, + { + "last-modified": "Wed, 19 Oct 2011 01:17:47 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "6641" + }, + { + "etag": "\"80af29e9fc8dcc1:8e4\"" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 165, + "wire": "88f86c96e4593e940854cb6d0a080112817ae019b8cbaa62d1bfe20f0d8313ad8b588ca47e561cc5802d380780077f6496dd6d5f4a082a435d8a08016540b7702fdc03ea62d1bf6196dc34fd280654d27eea0801128166e322b821298b46ffea", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Wed, 11 Jul 2012 18:03:37 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2752" + }, + { + "cache-control": "max-age=14608007" + }, + { + "expires": "Sun, 21 Apr 2013 15:19:09 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 166, + "wire": "88be6c96df697e9403aa65b68504003ea081702f5c684a62d1bf5f87352398ac4c697fc9c80f0d03363133c7c60f138ffe40cc820cad025948f86e37187f9f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + }, + { + "last-modified": "Tue, 07 Jul 2009 20:18:42 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "613" + }, + { + "expires": "Mon, 26 Nov 2012 16:35:19 GMT" + }, + { + "cache-control": "max-age=3888000" + }, + { + "etag": "\"03d21f40ffc91:5b1\"" + } + ] + }, + { + "seqno": 167, + "wire": "88768c86b19272ad78fe8e92b015c3f36c96df3dbf4a044a65b685040089410ae34d5c13ca62d1bfd80f0d03353531588ca47e561cc58041742fb6273f6496c361be940894cb6d0a080165410ae34d5c13ca62d1bfc4f07b8b84842d695b05443c86aa6f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 12 Jul 2012 22:44:28 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "551" + }, + { + "cache-control": "max-age=21719526" + }, + { + "expires": "Fri, 12 Jul 2013 22:44:28 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 168, + "wire": "88c56496d07abe940baa5f2914100225040b8cbf719694c5a37f6c96df697e941014dc5ad4100215002b807ae080a62d1bffded00f1390fe5e005e66491c7a31c8490371d103f9cfcd0f0d840bae3acf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + }, + { + "expires": "Mon, 17 Dec 2012 20:39:34 GMT" + }, + { + "last-modified": "Tue, 20 Sep 2011 02:08:20 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80183dd68badcd1:720\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "17673" + } + ] + }, + { + "seqno": 169, + "wire": "88c47f18a64b96d04afa762747d5670dbd55700191d14aa8aeb4ab80666582c8c8e3af15a944b03c840d7f0f28fca8f520a8418f5417a686fe7861c3b28ddaedd1b2fe686f5dfdff7e5ba79fbe6ceac5c01cfdcde740b078e7bf8d1a69d0d7edffde9aafec17ed3fb4eac5cfdf3e946bb3cdbb7eef9e919aa817b075a4f62e58c7ea42a08b90f4fde0f359ac2a20dd6d5f4a0195b49fbac20059502cdc645704253168dff7ac699e06145a839bd9ab5885aec3771a4b4085aec1cd48ff86a8eb10649cbf5f91497ca589d34d1f649c7620a98386fc2b3d5b842d4b70dd798624f6d5d4b27fce", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "t6ulcpjqcj9%3Fuk%601d72f%2B4%603g-13ac678e4f2-0x104" + }, + { + "set-cookie": "nonsession=CgADLAAFQlSPuMQDKACBZ+x5mYzY3OGU0YzgxM2EwYTVlNmM4ZDZjODQ2ZmZmOGYzYjlPrxuR;Domain=.raptor.ebaydesc.com;Expires=Sun, 03-Nov-2013 13:32:22 GMT;Path=/ " + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + } + ] + }, + { + "seqno": 170, + "wire": "88cbc36c96c361be940094d27eea0801128005c033704053168dffe50f0d846da642cf588ca47e561cc58190b40081c77f6496dc34fd280129a4fdd41002ca8005c033704fa98b46ffd1408721eaa8a4498f5788ea52d6b0e83772ffcb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 00:03:20 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "54313" + }, + { + "cache-control": "max-age=31401067" + }, + { + "expires": "Sat, 02 Nov 2013 00:03:29 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 171, + "wire": "88cfc76c96c361be940094d27eea0801128005c03371b1298b46ffe90f0d84101d007f588ca47e561cc58190b40081f67f6496dc34fd280129a4fdd41002ca8005c03371b6d4c5a37fd5c1ce", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 02 Nov 2012 00:03:52 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "20701" + }, + { + "cache-control": "max-age=31401093" + }, + { + "expires": "Sat, 02 Nov 2013 00:03:55 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 172, + "wire": "88d27f0ca6adaaeb9e4a3c11566fbfdcbf29544f3ad3aab802aaee05598560b23238ebc56c4cac0f216c9f5886a8eb10649cbfe5ed0f0d8365b75dd76401300f28ff9501c7be00b2d85f69f6c416de02a01042d89f540d09e75e75c540e09c0b2d36a819085b13ca81a13ce3c26550382700d34caa064216c4eaa0684f38f002a81c10197c0f2a008596c2fb4fb62744e3ea804fbc1540d2c1540e015032fbc0540d2c1540e015032fbafaa06960aa0700a81971e795034b0550380540c89b6d5034b055038054010b2d85f69f6da0bae015008216dd6d5034b0550380540c85b13aa81a582a81c02a065e13ea81a582a81c02a065f0895034b0550380540cbc2755034b0550380540cbceb8a81a582a81c02a065e136a81a582a81c02a065a659540d2c1540e015032171b0aa06960aa0700a8190b8d815034b0550380fb52f9e919aa82919aa5cb18fd589a5721e9fb5358d33c0c589a7cd", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9ve*t%28747%60e%7E%3A-13ac678e523-0x15c" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "3577" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + }, + { + "expires": "0" + }, + { + "set-cookie": "HT=1351949521580%0211529%04287876%06261345%0311528%04286823%06260443%0311527%04286801%06203908%011351949527269%02981%04-1%060%03980%04-1%060%03979%04-1%060%03688%04-1%060%03255%04-1%060%011351949541760%0211575%04-1%060%031527%04-1%060%03829%04-1%060%03912%04-1%060%03827%04-1%060%03876%04-1%060%03825%04-1%060%03433%04-1%060%031651%04-1%060%031650%04-1%060; Domain=main.ebayrtm.com; Path=/rtm" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 173, + "wire": "88d5cd6c96d07abe9413ea6a2254100225041b8266e34f298b46ffef0f0d836c2cbd588ca47e561cc581908591084fff6496df697e9413ea6a22541002ca820dc10ae36253168dff6196dc34fd280654d27eea0801128166e322b82654c5a37fc8d5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Mon, 29 Oct 2012 21:23:48 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "5138" + }, + { + "cache-control": "max-age=31132229" + }, + { + "expires": "Tue, 29 Oct 2013 21:22:52 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:23 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 174, + "wire": "88be6c96df3dbf4a01a535112a0800754106e34d5c65f53168dfdbe6e50f0d83642ebf6496c361be940b4a5f2914100225040b8066e05b53168dffe40f138ffe40cc820cad025948f86e37187f9f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:23 GMT" + }, + { + "last-modified": "Thu, 04 Oct 2007 21:44:39 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "3179" + }, + { + "expires": "Fri, 14 Dec 2012 20:03:15 GMT" + }, + { + "cache-control": "max-age=3888000" + }, + { + "etag": "\"03d21f40ffc91:5b1\"" + } + ] + }, + { + "seqno": 175, + "wire": "88db0f28bca2d275f4fc017db7de7c1f6a5f3d2335502e58c7e9721e9fb53079acd615106f9edfa50025b49fbac2005d502cdc645704ca98b46ffb5358d33c0c7fd2d1d35f95497ca58e83ee3412c3569fb24e3b1054c1c37e159e0f0d8374006bc16c96d07abe941094d444a820044a099b806ae044a62d1bff40884d83a903224c7abfcab7aaf67fb700ec7ffdfffa8fada4a64c922984d5486aa6d761e4b489eed7d6da0f364914adaae937855c0744c6faace1b7aaf62a2bae01d8d57702ae010b059191c75e24627560798ddf7f0aa44b96d04afa762747d5670dbd55700191d14aa8ae844ab808d60b23238ebc503e5581e480d3d2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lucky9=1959890; Domain=.ebay.com; Expires=Thu, 02-Nov-2017 13:32:23 GMT; Path=/" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/javascript;charset=UTF-8" + }, + { + "content-length": "7004" + }, + { + "date": "Sat, 03 Nov 2012 13:32:23 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 23:04:12 GMT" + }, + { + "transaction": "uk.r+607b~`s,RcmdId FindingProductv4,RlogId p4pmiw%60jtb9%3Fuk.r%2B607b%7E%60s-13ac678cb27-0xb7" + }, + { + "rlogid": "t6ulcpjqcj9%3Fuk%601d72f%2B12%60b-13ac678e09e-0xc0" + }, + { + "content-language": "en-US" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 176, + "wire": "88c46496dd6d5f4a09b5349fba820044a099b8d3b700053168df6c96c361be9413aa436cca0801028005c10ae36d298b46ffe2edecea0f0d033336360f1390fe5e00e513e57e523d21081b8f15afe7d0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:23 GMT" + }, + { + "expires": "Sun, 25 Nov 2012 23:47:00 GMT" + }, + { + "last-modified": "Fri, 27 Aug 2010 00:22:54 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "366" + }, + { + "etag": "\"80af29e9fc8dcc1:8e4\"" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 177, + "wire": "8bc66496c361be94138a6a225410022500d5c699b8c854c5a37f6c96df3dbf4a01c535112a08006d4106e09fb81694c5a37fe4ef0f1391fe5e04b1b8f95d65c71a2321b8e4a5fe7fee0f0d820b41ecd2", + "headers": [ + { + ":status": "304" + }, + { + "date": "Sat, 03 Nov 2012 13:32:23 GMT" + }, + { + "expires": "Fri, 26 Oct 2012 04:43:31 GMT" + }, + { + "last-modified": "Thu, 06 Oct 2005 21:29:14 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80fb69e73664c31:6fe\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "141" + }, + { + "cache-control": "max-age=3888000" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 178, + "wire": "88c86c96d07abe94136a65b685040036a0817190dc69953168dfe5f0ef0f0d0236346497dd6d5f4a09b5349fba820044a099b8d3971b7d4c5a37ffee0f138ffe40cc820cad025948f86e37187f9f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:23 GMT" + }, + { + "last-modified": "Mon, 25 Jul 2005 20:31:43 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "64" + }, + { + "expires": "Sun, 25 Nov 2012 23:46:59 GMT" + }, + { + "cache-control": "max-age=3888000" + }, + { + "etag": "\"03d21f40ffc91:5b1\"" + } + ] + }, + { + "seqno": 179, + "wire": "88ca6496dd6d5f4a05e5349fba820044a085704f5c034a62d1bf6c96df697e94136a6e2d6a080112806ee05cb81794c5a37f5f87352398ac5754dff4f3f10f0d8375c1330f138ffe40471e7a371b0b448c371b79cfe7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:23 GMT" + }, + { + "expires": "Sun, 18 Nov 2012 22:28:04 GMT" + }, + { + "last-modified": "Tue, 25 Sep 2012 05:16:18 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "7623" + }, + { + "etag": "\"0c688b6514cb1:586\"" + } + ] + }, + { + "seqno": 180, + "wire": "88e8e06c96c361be940bea6a2254100225042b8d8ae32253168dff5f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ec938ec4153070df8567bf0f0d03393439588ca47e561cc5819009d65c083f6496dc34fd2817d4d444a8200595042b8d8ae32253168dffefdbe8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Fri, 19 Oct 2012 22:52:32 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "949" + }, + { + "cache-control": "max-age=30273610" + }, + { + "expires": "Sat, 19 Oct 2013 22:52:32 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:22 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 181, + "wire": "885f911d75d0620d263d4c795ba0fb8d04b0d5a76c96df697e940bea65b6a50400894037700f5c684a62d1bf52848fd24a8f768dd06258741e54ad9326e61c5c1fe80f0d83081c7fec588ca47e561cc5802fb8e8190bdf6496e4593e940bea65b6a50400b2a01bb8c86e002a62d1bfd7e1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Tue, 19 Jun 2012 05:08:42 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1069" + }, + { + "vary": "Accept-Encoding" + }, + { + "cache-control": "max-age=19670318" + }, + { + "expires": "Wed, 19 Jun 2013 05:31:01 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:23 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 182, + "wire": "88f2ea6c96df3dbf4a09b535112a0801128066e05ab82654c5a37fc70f0d8371f643588ca47e561cc581903a20b217ff6496c361be94136a6a22541002ca8066e05ab821298b46ffdae4f1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Thu, 25 Oct 2012 03:14:23 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "6931" + }, + { + "cache-control": "max-age=30721319" + }, + { + "expires": "Fri, 25 Oct 2013 03:14:22 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:23 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 183, + "wire": "88f54083b283cd9cb6755c074eaab37dfefd3e52871d5c4165972612c1646471d78a595e408df2b1631fa5ac2f6b4a84ac693fb70b23238ebc558b2bc0586d8c8b01d700b104e02594206a364bfc0fa0fcae3a285e62a7f808177c0b85f12e10bdfc1631fa5c87a7ff7ffc5f8b1d75d0620d263d4c7441eaeb6196dc34fd280654d27eea0801128166e322b82694c5a37f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlog": "uh%60jk%3D9vj*ts67.21336g2-13ac678eef8" + }, + { + "x-ebay-request-id": "13ac678e-ef80-a5ac-0760-c260ff104b3e!ajax.all.get!10.90.192.118!ebay.com[]" + }, + { + "content-type": "application/json" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 13:32:24 GMT" + } + ] + }, + { + "seqno": 184, + "wire": "886196dc34fd280654d27eea0801128166e322b826d4c5a37fd26c96df3dbf4a01a535112a080112817ae36e5c65c53168df5f87352398ac4c697fcbca588ba47e561cc581979e7800070f0d837db1070f138ffe40471e7a371b0b448c371b79cfe7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:25 GMT" + }, + { + "expires": "Sun, 18 Nov 2012 22:28:04 GMT" + }, + { + "last-modified": "Thu, 04 Oct 2012 18:56:36 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "9521" + }, + { + "etag": "\"0c688b6514cb1:586\"" + } + ] + }, + { + "seqno": 185, + "wire": "88768c86b19272ad78fe8e92b015c37f1eabadaa9570165bd25b64a3c11566fbf6d4abb85a99c6d5700a89e6471aacdf582c8c8e3af1657c2b03c85f27588caec3771a4bf4a547588324e5f65f87497ca58e883d5ff4c5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4n%60rujfudlwc%3D9un%7F4g65%60%283ab%3D-13ac678ef91-0x19c" + }, + { + "cache-control": "private, no-cache" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/json" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 13:32:25 GMT" + } + ] + }, + { + "seqno": 186, + "wire": "88c10f28bca2d275f4fc017db7de7c1f6a5f3d2335502e58c7e9721e9fb53079acd615106f9edfa50025b49fbac2005d502cdc6457190298b46ffb5358d33c0c7ff8f7f9f60f0d847c0265bf6196dc34fd280654d27eea0801128166e322b8c814c5a37fe3e2e1f6f5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lucky9=1959890; Domain=.ebay.com; Expires=Thu, 02-Nov-2017 13:32:30 GMT; Path=/" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-length": "90235" + }, + { + "date": "Sat, 03 Nov 2012 13:32:30 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 23:04:12 GMT" + }, + { + "transaction": "uk.r+607b~`s,RcmdId FindingProductv4,RlogId p4pmiw%60jtb9%3Fuk.r%2B607b%7E%60s-13ac678cb27-0xb7" + }, + { + "rlogid": "t6ulcpjqcj9%3Fuk%601d72f%2B12%60b-13ac678e09e-0xc0" + }, + { + "content-language": "en-US" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 187, + "wire": "8b6196dc34fd280654d27eea0801128166e322b8c854c5a37f6496df3dbf4a09d53716b50400894133702ddc680a62d1bf6c96c361be941054ca3a94100215040b8d8ae36253168dffc7d4d3c60f0d033336360f1390fe4031baf0a31c91be48c371c785fcfff4", + "headers": [ + { + ":status": "304" + }, + { + "date": "Sat, 03 Nov 2012 13:32:31 GMT" + }, + { + "expires": "Thu, 27 Sep 2012 23:15:40 GMT" + }, + { + "last-modified": "Fri, 21 Jan 2011 20:52:52 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "366" + }, + { + "etag": "\"0aa782badb9cb1:682\"" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 188, + "wire": "88c50f28bca2d275f4fc017db7de7c1f6a5f3d2335502e58c7e9721e9fb53079acd615106f9edfa50025b49fbac2005d502cdc6457191298b46ffb5358d33c0c7f5885aec3771a4b4085aec1cd48ff86a8eb10649cbf5a839bd9abea0f0d8478006c1fc3e97f29c7b7aaf67fb700ec7ffd98ff5b494c99245309aa90d54daec3c96913ddafadb41e6c92295b55d26f0ab80e898df559c36f55ec54575c03b1aaee098eb059191c75f00c60581e6373e85b842d4b70dd798624f6d5d4b27f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lucky9=1959890; Domain=.ebay.com; Expires=Thu, 02-Nov-2017 13:32:32 GMT; Path=/" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/javascript;charset=UTF-8" + }, + { + "content-length": "80050" + }, + { + "date": "Sat, 03 Nov 2012 13:32:31 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 23:04:12 GMT" + }, + { + "transaction": "uk.r+607b~go,RcmdId FindingProductv4,RlogId p4pmiw%60jtb9%3Fuk.r%2B607b%7Ego-13ac6790aa0-0xb6" + }, + { + "rlogid": "t6ulcpjqcj9%3Fuk%601d72f%2B12%60b-13ac678e09e-0xc0" + }, + { + "content-language": "en-US" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 189, + "wire": "8b6196dc34fd280654d27eea0801128166e322b8c894c5a37fe8e7cedbdacd0f0d033336360f1390fe4031baf0a31c91be48c371c785fcff408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "304" + }, + { + "date": "Sat, 03 Nov 2012 13:32:32 GMT" + }, + { + "expires": "Fri, 26 Oct 2012 04:43:31 GMT" + }, + { + "last-modified": "Thu, 06 Oct 2005 21:29:14 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "366" + }, + { + "etag": "\"0aa782badb9cb1:682\"" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 190, + "wire": "88bf6496c361be940b4a5f291410022502cdc659b80654c5a37f6c96e4593e940094d03f4a0801128266e01fb827d4c5a37fcfd05f88352398ac74acb37f0f0d830b6d35", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:32 GMT" + }, + { + "expires": "Fri, 14 Dec 2012 13:33:03 GMT" + }, + { + "last-modified": "Wed, 02 May 2012 23:09:29 GMT" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1544" + } + ] + }, + { + "seqno": 191, + "wire": "88d07f10a9adaa9570165bd25b64a3c11566fbf6d4abb85a99c6d5700a89e204b32c1646471d7c20902b03c85d7fcfc8d3c4c30f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4n%60rujfudlwc%3D9un%7F4g65%60%28c1eg-13ac67910d1-0x179" + }, + { + "cache-control": "private, no-cache" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 13:32:32 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 192, + "wire": "88d16c96df697e9413ea681fa50400894106e32cdc65a53168dfc00f0d8369f03d588ba47e561cc581e640dbc26b6496df3dbf4a01d53096350400b2a05cb8d0ae36ea98b46f6196dc34fd280654d27eea0801128166e322b8cb2a62d1bfc6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Tue, 29 May 2012 21:33:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4908" + }, + { + "cache-control": "max-age=8305824" + }, + { + "expires": "Thu, 07 Feb 2013 16:42:57 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:33 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 193, + "wire": "88d50f28bca2d275f4fc017db7de7c1f6a5f3d2335502e58c7e9721e9fb53079acd615106f9edfa50025b49fbac2005d502cdc645719714c5a37fda9ac699e063fcdcccb5f91497ca589d34d1f649c7620a98386fc2b3d0f0d847c21641f6196dc34fd280654d27eea0801128166e322b8cb8a62d1bf6c96d07abe941094d444a820044a099b806ae044a62d1bffcdf7cccb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lucky9=1959890; Domain=.ebay.com; Expires=Thu, 02-Nov-2017 13:32:36 GMT; Path=/" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-length": "91130" + }, + { + "date": "Sat, 03 Nov 2012 13:32:36 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 23:04:12 GMT" + }, + { + "transaction": "uk.r+607b~go,RcmdId FindingProductv4,RlogId p4pmiw%60jtb9%3Fuk.r%2B607b%7Ego-13ac6790aa0-0xb6" + }, + { + "rlogid": "t6ulcpjqcj9%3Fuk%601d72f%2B12%60b-13ac678e09e-0xc0" + }, + { + "content-language": "en-US" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 194, + "wire": "8b6196dc34fd280654d27eea0801128166e322b8cbaa62d1bfd3d2dbe8e7da0f0d033336360f1390fe4031baf0a31c91be48c371c785fcffca", + "headers": [ + { + ":status": "304" + }, + { + "date": "Sat, 03 Nov 2012 13:32:37 GMT" + }, + { + "expires": "Thu, 27 Sep 2012 23:15:40 GMT" + }, + { + "last-modified": "Fri, 21 Jan 2011 20:52:52 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "366" + }, + { + "etag": "\"0aa782badb9cb1:682\"" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 195, + "wire": "88d90f28bca2d275f4fc017db7de7c1f6a5f3d2335502e58c7e9721e9fb53079acd615106f9edfa50025b49fbac2005d502cdc645719754c5a37fda9ac699e063fd1d0cf5f95497ca58e83ee3412c3569fb24e3b1054c1c37e159e0f0d847800701fbfc07f10c6eea2f67fb702e4824dbf5b494c99245309aa90d54daec3c96913ddafadb41e6c92295b55d26f0ab80e898df559c3dd5770af62a2bae05c9049b560b23238ebe19657d60798c97f09a44b96d04afa762747d5670dbd55700191d14aa8ae844ab808d60b23238ebc503e5581e480d0cf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lucky9=1959890; Domain=.ebay.com; Expires=Thu, 02-Nov-2017 13:32:37 GMT; Path=/" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/javascript;charset=UTF-8" + }, + { + "content-length": "80060" + }, + { + "date": "Sat, 03 Nov 2012 13:32:37 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 23:04:12 GMT" + }, + { + "transaction": "v .r+616d2tu,RcmdId FindingProductv4,RlogId p4pmiw%60jtb9%3Fv%7F.r%2B616d2tu-13ac6791ff9-0xbc" + }, + { + "rlogid": "t6ulcpjqcj9%3Fuk%601d72f%2B12%60b-13ac678e09e-0xc0" + }, + { + "content-language": "en-US" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 196, + "wire": "8b6196dc34fd280654d27eea0801128166e322b8cbca62d1bf6496c361be94138a6a225410022500d5c699b8c854c5a37f6c96df3dbf4a01c535112a08006d4106e09fb81694c5a37fe1eeede00f0d033336360f1390fe4031baf0a31c91be48c371c785fcffd0", + "headers": [ + { + ":status": "304" + }, + { + "date": "Sat, 03 Nov 2012 13:32:38 GMT" + }, + { + "expires": "Fri, 26 Oct 2012 04:43:31 GMT" + }, + { + "last-modified": "Thu, 06 Oct 2005 21:29:14 GMT" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "366" + }, + { + "etag": "\"0aa782badb9cb1:682\"" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 197, + "wire": "88df6c96d07abe9413aa6e2d6a080102807ee01db82694c5a37ff4d4d60f0d83089f077b8b84842d695b05443c86aa6f5892aec3771a4bf4a523f2b0e62c0c85b65c00016497dd6d5f4a0195349fba820059502cdc6457197d4c5a37ff6196dc34fd280654d27eea0801128166e322b8cbea62d1bfd5408a224a7aaa4ad416a9933f8369b79e", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 27 Sep 2010 09:07:24 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1290" + }, + { + "vary": "Accept-Encoding" + }, + { + "cache-control": "private, max-age=31536000" + }, + { + "expires": "Sun, 03 Nov 2013 13:32:39 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:39 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cteonnt-length": "4588" + } + ] + }, + { + "seqno": 198, + "wire": "88e57f08aaadaa9570165bd25b64a3c11566fbf6d4abb85a99c715700a89e664640b059191c75f13d21160790beeffe4dde8d9c00f0d0234320f28faaab31a08d335a6918238ebcf332842c8c036dc7dc68ae34e11c96518c23205b13ae36075efff0935a6918238ebcf364702c8c0300df95c6dc7a30b92cadbe174a56c4eb8d81d7bffcfb52f9e919aa8172c63f4b90f4fda983cd66b0a88375b57d280656d27eeb08016540b371915c680a62d1bfed4d634cf031f4003703370d2acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5ee1b46a437f40d4bf8388d4d7baf9d4d7ba11a9ab86d53743a0ea64d37d4e1a72297b568534c3c54c9a77a9bb7c2a5fc1a14d7b707f3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4n%60rujfudlwc%3D9un%7F4g66%60%283d30-13ac67928dc-0x197" + }, + { + "cache-control": "private, no-cache" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 13:32:39 GMT" + }, + { + "content-length": "42" + }, + { + "set-cookie": "npii=btguid/c67883f113a0a56964e646c6ffaa1ac152765078^cguid/c67885c613a0a0a9f6568b16ff5917ee52765078^; Domain=.ebay.com; Expires=Sun, 03-Nov-2013 13:32:40 GMT; Path=/" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa ADMa DEVa PSDo PSAa OUR SAMo IND UNI COM NAV INT STA DEM PRE\"" + } + ] + }, + { + "seqno": 199, + "wire": "88e70f28bca2d275f4fc017db7de7c1f6a5f3d2335502e58c7e9721e9fb53079acd615106f9edfa50025b49fbac2005d502cdc6457197d4c5a37fda9ac699e063fdfdeddcf0f0d847c2171bfc1cdcac9dbda", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lucky9=1959890; Domain=.ebay.com; Expires=Thu, 02-Nov-2017 13:32:39 GMT; Path=/" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-length": "91165" + }, + { + "date": "Sat, 03 Nov 2012 13:32:39 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 23:04:12 GMT" + }, + { + "transaction": "v .r+616d2tu,RcmdId FindingProductv4,RlogId p4pmiw%60jtb9%3Fv%7F.r%2B616d2tu-13ac6791ff9-0xbc" + }, + { + "rlogid": "t6ulcpjqcj9%3Fuk%601d72f%2B12%60b-13ac678e09e-0xc0" + }, + { + "content-language": "en-US" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 200, + "wire": "88e77f00aaadaa9570165bd25b64a3c11566fbf6d4abb85a99c6d5700a89e6db6e5582c8c8e3af8a40b6b03c85e93fe6dfeadb6196dc34fd280654d27eea0801128166e322b8d054c5a37f0f0d0234320f28faaab31a08c935a6918238ebcf364702c8c0300df95c6dc7a30b92cadbe174a56c4eb8d81d7fffc4cd69a4608e3af3ccca10b2300db71f71a2b8d384725946308c816c4eb8d81d7fffcfb52f9e919aa8172c63f4b90f4fda983cd66b0a88375b57d280656d27eeb08016540b371915c682a62d1bfed4d634cf031fc0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4n%60rujfudlwc%3D9un%7F4g65%60%28555f-13ac6792d15-0x18d" + }, + { + "cache-control": "private, no-cache" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 13:32:41 GMT" + }, + { + "content-length": "42" + }, + { + "set-cookie": "npii=bcguid/c67885c613a0a0a9f6568b16ff5917ee52765079^tguid/c67883f113a0a56964e646c6ffaa1ac152765079^; Domain=.ebay.com; Expires=Sun, 03-Nov-2013 13:32:41 GMT; Path=/" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa ADMa DEVa PSDo PSAa OUR SAMo IND UNI COM NAV INT STA DEM PRE\"" + } + ] + }, + { + "seqno": 201, + "wire": "48826401ea0f28bca2d275f4fc017db7de7c1f6a5f3d2335502e58c7e9721e9fb53079acd615106f9edfa50025b49fbac2005d502cdc64571a1298b46ffb5358d33c0c7fe2e10f1f9e9d29aee30c78f1e172c63f4b90f4b128d1398f531394742675a328ed4faf7f01a2adaae937855c0744c6faace1eeabb857f0422ae0249dd12acde582c8c8e3afb2d0050f0d01306196dc34fd280654d27eea0801128166e322b8d094c5a37f", + "headers": [ + { + ":status": "301" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lucky9=1959890; Domain=.ebay.com; Expires=Thu, 02-Nov-2017 13:32:42 GMT; Path=/" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "location": "http://www.ebay.com/fashion/health-beauty" + }, + { + "rlogid": "p4pmiw%60jtb9%3Fv%7F.wcc%60dh72%3C-13ac6793402" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:32:42 GMT" + } + ] + }, + { + "seqno": 202, + "wire": "88ec0f28bca2d275f4fc017db7de7c1f6a5f3d2335502e58c7e9721e9fb53079acd615106f9edfa50025b49fbac2005d502cdc64571a654c5a37fda9ac699e063f5886a8eb10649cbfe4e3d50f0d84089d7c5f6196dc34fd280654d27eea0801128166e322b8d32a62d1bfd4d17f02a8adab557009474966eb5ca6b65559c2ab3793d9513ddbb2f22ae01b995700db2b059191c75f659724e3e2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lucky9=1959890; Domain=.ebay.com; Expires=Thu, 02-Nov-2017 13:32:43 GMT; Path=/" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-length": "12792" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 23:04:12 GMT" + }, + { + "transaction": "v .r+616d2tu,RcmdId FindingProductv4,RlogId p4pmiw%60jtb9%3Fv%7F.r%2B616d2tu-13ac6791ff9-0xbc" + }, + { + "rlogid": "p4u%60tsjfgkpfiuf%3F%3Ctq%28qq.d%605g%6053-13ac679336d" + }, + { + "content-language": "en-US" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 203, + "wire": "88bf6497dd6d5f4a09b5349fba820044a099b8d3b71a6d4c5a37ff6c96df3dbf4a01b532db42820044a05eb8d33702f298b46f5f87352398ac5754df52848fd24a8f768dd06258741e54ad9326e61c5c1ff50f0d83134e3f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "expires": "Sun, 25 Nov 2012 23:47:45 GMT" + }, + { + "last-modified": "Thu, 05 Jul 2012 18:43:18 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "2469" + } + ] + }, + { + "seqno": 204, + "wire": "88f46c96df3dbf4a09a5340fd2820044a05cb8215c13aa62d1bfe30f0d836de6c1588ca47e561cc58190b607df6dcf6497dd6d5f4a0195349fba820059500e5c0bd7197d4c5a37ffc7e854012a", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 24 May 2012 16:22:27 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5850" + }, + { + "cache-control": "max-age=31509956" + }, + { + "expires": "Sun, 03 Nov 2013 06:18:39 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "access-control-allow-origin": "*" + } + ] + }, + { + "seqno": 205, + "wire": "88768c86b19272ad78fe8e92b015c3d75f90497ca582211f649c7620a98386fc2b3deef00f0d836dc75fd75892aed8e8313e94a47e561cc58190b6cb80003f6497dd6d5f4a0195349fba820059502cdc64571a654c5a37ffccedd5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 27 Sep 2010 09:07:24 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "5679" + }, + { + "vary": "Accept-Encoding" + }, + { + "cache-control": "public, max-age=31536000" + }, + { + "expires": "Sun, 03 Nov 2013 13:32:43 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cteonnt-length": "4588" + } + ] + }, + { + "seqno": 206, + "wire": "88c16c96dc34fd282754d444a820044a05bb8db571b694c5a37feb0f0d8371910b588ca47e561cc58190b62105f77f6496dd6d5f4a0195349fba820059500fdc68571a0298b46fcff0c5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Sat, 27 Oct 2012 15:54:54 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6322" + }, + { + "cache-control": "max-age=31522197" + }, + { + "expires": "Sun, 03 Nov 2013 09:42:40 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "access-control-allow-origin": "*" + } + ] + }, + { + "seqno": 207, + "wire": "88c47f0fa3adaaeb9e4a3c11566fbf6aaee0f94aa279d6c1301da960b23191f8df23a0581e42eb9f6c96e4593e94134a6a2254100225042b827ee05f53168dff7f19c7bdae0fe6f70da3521bfa06a5fc1c46a6bdd09d4d7baf9d4d5c36a97786e53869c8a6be1b54c9a77a97f0685376f854d7b70297b568534c3c54d5bef29a756452feed6a5ed5b7f95f911d75d0620d263d4c795ba0fb8d04b0d5a70f0d821360588ca47e561cc581908402036cff6496df697e9413ea6a22541002ca8166e001702e298b46ffd5f65a839bd9abe3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9u%7E*t%28750g07n-13aac9b9c70-0x176" + }, + { + "last-modified": "Wed, 24 Oct 2012 22:29:19 GMT" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "250" + }, + { + "cache-control": "max-age=31102053" + }, + { + "expires": "Tue, 29 Oct 2013 13:00:16 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 208, + "wire": "88cb6c96d07abe94138a5f291410021502edc659b826d4c5a37ff50f0d836df003588ca47e561cc58190b6069e0b9f6497dd6d5f4a0195349fba820059500d5c6c571b7d4c5a37ffd9408721eaa8a4498f5788ea52d6b0e83772ffd0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 26 Dec 2011 17:33:25 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5900" + }, + { + "cache-control": "max-age=31504816" + }, + { + "expires": "Sun, 03 Nov 2013 04:52:59 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "access-control-allow-origin": "*" + } + ] + }, + { + "seqno": 209, + "wire": "88cf6c96d07abe9413ea6a225410022502ddc64571b794c5a37f5f88352398ac74acb37f0f0d8365f6d9588ca47e561cc58190b610bc16ff6497dd6d5f4a0195349fba820059500e5c69fb8cbca62d1bffdec2d4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 29 Oct 2012 15:32:58 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3953" + }, + { + "cache-control": "max-age=31511815" + }, + { + "expires": "Sun, 03 Nov 2013 06:49:38 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "access-control-allow-origin": "*" + } + ] + }, + { + "seqno": 210, + "wire": "88d36c96c361be940894d444a820044a05db8cbb700f298b46ffc10f0d8369a0bd588ca47e561cc5804fb8265c7c5f6496dc34fd281129a88950400b2a0417040b8db6a62d1bffe1c5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Fri, 12 Oct 2012 17:37:08 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4418" + }, + { + "cache-control": "max-age=29623692" + }, + { + "expires": "Sat, 12 Oct 2013 10:20:55 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 211, + "wire": "88d6ef5f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ec938ec4153070df8567bf5b842d4b70ddf0cb0f0d840800fbdfefd5e3c7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 27 Sep 2010 09:07:24 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "10098" + }, + { + "cache-control": "private, max-age=31536000" + }, + { + "expires": "Sun, 03 Nov 2013 13:32:43 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 212, + "wire": "88e36c96df697e9403aa436cca080112820dc006e09b53168dffe0df0f1390fe5e034065f216495d689206e38067f9de0f0d850859138e7f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "last-modified": "Tue, 07 Aug 2012 21:01:25 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"804039cedf74cd1:603\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "113266" + } + ] + }, + { + "seqno": 213, + "wire": "88d96c96df697e9403aa436cca080112806ee09fb8db6a62d1bfc70f0d83759683588ca47e561cc5802fb2f3816ddf6496dc34fd2816d4cb6d4a080165410ae32ddc1014c5a37fe7cbdd", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Tue, 07 Aug 2012 05:29:55 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7341" + }, + { + "cache-control": "max-age=19386157" + }, + { + "expires": "Sat, 15 Jun 2013 22:35:20 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "access-control-allow-origin": "*" + } + ] + }, + { + "seqno": 214, + "wire": "88dc6c96df697e94132a6a2254100225041b8dbb702da98b46ffca0f0d8375a705588ca47e561cc58190b6079b65af6497dd6d5f4a0195349fba820059500ddc6dab8dbaa62d1bffeacee0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Tue, 23 Oct 2012 21:57:15 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7462" + }, + { + "cache-control": "max-age=31508534" + }, + { + "expires": "Sun, 03 Nov 2013 05:54:57 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "access-control-allow-origin": "*" + } + ] + }, + { + "seqno": 215, + "wire": "88df6c96d07abe9413ea6a2254100225002b8cbf704e298b46ffcd0f0d840800f83f588ca47e561cc58190b600b217bf6497dd6d5f4a0195349fba820059500cdc6dab8d054c5a37ffedd1e3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 29 Oct 2012 02:39:26 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "10090" + }, + { + "cache-control": "max-age=31501318" + }, + { + "expires": "Sun, 03 Nov 2013 03:54:41 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "access-control-allow-origin": "*" + } + ] + }, + { + "seqno": 216, + "wire": "88e2d56c96df697e940b8a6a225410022502fdc64571a6d4c5a37fe20f0d8371975d588ba47e561cc58190000268026496e4593e940b8a6a22541002ca817ee322b8d36a62d1bff0d47b8b84842d695b05443c86aa6f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 16 Oct 2012 19:32:45 GMT" + }, + { + "content-type": "text/css;charset=UTF-8" + }, + { + "content-length": "6377" + }, + { + "cache-control": "max-age=30002402" + }, + { + "expires": "Wed, 16 Oct 2013 19:32:45 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 217, + "wire": "88e66c96df697e9413ea681fa504008940bf71a05c03aa62d1bfd40f0d8369a79e588ba47e561cc581d138065f6f6496dc34fd282714ca3a941002ca816ae05fb81794c5a37ff4d8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Tue, 29 May 2012 19:40:07 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4488" + }, + { + "cache-control": "max-age=7260395" + }, + { + "expires": "Sat, 26 Jan 2013 14:19:18 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 218, + "wire": "88e96c96c361be941014cb6d0a0801128266e09fb8cbca62d1bfd70f0d8369e79f588ca47e561cc5802fbe079a103f6496c361be941054cb6d4a08016541337197ee34ca98b46ff7db", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Fri, 20 Jul 2012 23:29:38 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4889" + }, + { + "cache-control": "max-age=19908420" + }, + { + "expires": "Fri, 21 Jun 2013 23:39:43 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 219, + "wire": "88ec6c96df697e9403ea6a225410022502d5c00ae05d53168dffda0f0d8365c65e588ca47e561cc5804f36075e781f6496dd6d5f4a09f53716b50400b2a045704d5c032a62d1bf6196dc34fd280654d27eea0801128166e322b8d32a62d1bfdf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Tue, 09 Oct 2012 14:02:17 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3638" + }, + { + "cache-control": "max-age=28507880" + }, + { + "expires": "Sun, 29 Sep 2013 12:24:03 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 220, + "wire": "88f06c96df697e940094d444a820044a05eb8015c03ca62d1bffde0f0d836d96dc588ca47e561cc5804f85f79f7dbf6496d07abe9403aa6a22541002ca8115c10ae32f298b46ffc1e2f4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Tue, 02 Oct 2012 18:02:08 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5356" + }, + { + "cache-control": "max-age=29198995" + }, + { + "expires": "Mon, 07 Oct 2013 12:22:38 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "access-control-allow-origin": "*" + } + ] + }, + { + "seqno": 221, + "wire": "88f36c96df3dbf4a044a65b6850400894006e36e5c038a62d1bfe10f0d8369c03b588ca47e561cc5804069913e06ff6496c361be9413ca65b6a50400b2a0037041b80794c5a37fc4e5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 12 Jul 2012 01:56:06 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4607" + }, + { + "cache-control": "max-age=20432905" + }, + { + "expires": "Fri, 28 Jun 2013 01:21:08 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 222, + "wire": "88f66c96d07abe94132a65b68504008940b9702f5c03ca62d1bfe40f0d836c227b588ca47e561cc5804cbacbceb40f6496d07abe94036a436cca080165403b7197ae09953168dfc7e8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 23 Jul 2012 16:18:08 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5128" + }, + { + "cache-control": "max-age=23738740" + }, + { + "expires": "Mon, 05 Aug 2013 07:38:23 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 223, + "wire": "88f96c96d07abe9413ea6a225410022502e5c69ab8cb6a62d1bfe70f0d8375e7c5588ca47e561cc58190b6cb4ebeff6496dd6d5f4a0195349fba820059502cdc08ae34253168dfcaeb54012a", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 29 Oct 2012 16:44:35 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7892" + }, + { + "cache-control": "max-age=31534799" + }, + { + "expires": "Sun, 03 Nov 2013 13:12:42 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "access-control-allow-origin": "*" + } + ] + }, + { + "seqno": 224, + "wire": "88768c86b19272ad78fe8e92b015c36c96df697e940b8a6a225410022500cdc659b82654c5a37fec0f0d83684cb5588ca47e561cc5804fba271a79ef6496dd6d5f4a059535112a08016540b571b6ae042a62d1bfcff0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Tue, 16 Oct 2012 03:33:23 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4234" + }, + { + "cache-control": "max-age=29726488" + }, + { + "expires": "Sun, 13 Oct 2013 14:54:11 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 225, + "wire": "88c16c96d07abe94101486d994100225040b8272e34fa98b46ffef0f0d8364020f588ca47e561cc5804e080d32d8bf6496d07abe940094dc5ad41002ca8205c64371b6d4c5a37fd2f3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 20 Aug 2012 20:26:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3021" + }, + { + "cache-control": "max-age=26204352" + }, + { + "expires": "Mon, 02 Sep 2013 20:31:55 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 226, + "wire": "88c46c96c361be9413ca6e2d6a080112816ee085704da98b46fff20f0d8369f0bf588ca47e561cc581903a17dc79ff6496c361be94136a6a22541002ca8015c69db8c894c5a37fd5f6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Fri, 28 Sep 2012 15:22:25 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4919" + }, + { + "cache-control": "max-age=30719689" + }, + { + "expires": "Fri, 25 Oct 2013 02:47:32 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 227, + "wire": "88c76c96dd6d5f4a084a65b68504008940bd702cdc136a62d1bff50f0d8369d79e588ca47e561cc580407990b816ff6496df697e940094cb6d0a08016540b9700e5c0bea62d1bf6196dc34fd280654d27eea0801128166e322b8d34a62d1bffa", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Sun, 22 Jul 2012 18:13:25 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4788" + }, + { + "cache-control": "max-age=20831615" + }, + { + "expires": "Tue, 02 Jul 2013 16:06:19 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:44 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 228, + "wire": "88cb6c96c361be94038a65b6850400894102e05bb82794c5a37ff90f0d8365f79d588ca47e561cc5802ebceb2fb20f6496e4593e9413ea681fa50400b2a0417190dc65a53168dfc1408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Fri, 06 Jul 2012 20:15:28 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3987" + }, + { + "cache-control": "max-age=17873930" + }, + { + "expires": "Wed, 29 May 2013 10:31:34 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:44 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 229, + "wire": "88cf6c96df3dbf4a05c521b66504008940b571b66e002a62d1bf5f88352398ac74acb37f0f0d8369c133588ca47e561cc5804069d75c6dbf6497c361be9413ca65b6a50400b2a059b8d3971b7d4c5a37ffc6c2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 16 Aug 2012 14:53:01 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4623" + }, + { + "cache-control": "max-age=20477655" + }, + { + "expires": "Fri, 28 Jun 2013 13:46:59 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:44 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 230, + "wire": "88d36c96c361be9413aa65b68504008940bb71b7ae01b53168dfc10f0d8369e107588ca47e561cc5804069f7dd65ef6496c361be9413ca65b6a50400b2a05fb8db7700253168dfc9c5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Fri, 27 Jul 2012 17:58:05 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4821" + }, + { + "cache-control": "max-age=20499738" + }, + { + "expires": "Fri, 28 Jun 2013 19:55:02 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:44 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 231, + "wire": "88d66c96c361be940b2a65b685040089403771976e09a53168dfc40f0d836c0f03588ca47e561cc5802fbcf3a269bf6496c361be941054cb6d4a08016540bb71a72e34fa98b46fccc8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Fri, 13 Jul 2012 05:37:24 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5080" + }, + { + "cache-control": "max-age=19887245" + }, + { + "expires": "Fri, 21 Jun 2013 17:46:49 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:44 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 232, + "wire": "885f911d75d0620d263d4c795ba0fb8d04b0d5a76c96c361be940094d27eea080112817ee00571b1298b46ff52848fd24a8f768dd06258741e54ad9326e61c5c1f5a839bd9ab0f0d840bcf38cff6588aa47e561cc581a03cd83f6496dd6d5f4a01a5349fba820044a00171b66e32ca98b46feecf", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/x-javascript" + }, + { + "last-modified": "Fri, 02 Nov 2012 19:02:52 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "18863" + }, + { + "vary": "Accept-Encoding" + }, + { + "cache-control": "max-age=40850" + }, + { + "expires": "Sun, 04 Nov 2012 00:53:33 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 233, + "wire": "88e06c96df3dbf4a09d53716b504008940b3704d5c680a62d1bfce0f0d840800c83f588ca47e561cc58190b620b6217f6496dd6d5f4a0195349fba820059500fdc643704da98b46ff1d2e4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Thu, 27 Sep 2012 13:24:40 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "10030" + }, + { + "cache-control": "max-age=31521522" + }, + { + "expires": "Sun, 03 Nov 2013 09:31:25 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "access-control-allow-origin": "*" + } + ] + }, + { + "seqno": 234, + "wire": "88e36c96d07abe940b8a65b685040089403b700d5c0bea62d1bfd10f0d83719103588ca47e561cc5804cb2eb2269df6496df3dbf4a002a436cca080165400ae01cb8d854c5a37fd9d5e7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 16 Jul 2012 07:04:19 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6320" + }, + { + "cache-control": "max-age=23373247" + }, + { + "expires": "Thu, 01 Aug 2013 02:06:51 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:44 GMT" + }, + { + "connection": "keep-alive" + }, + { + "access-control-allow-origin": "*" + } + ] + }, + { + "seqno": 235, + "wire": "88e64085b283cc693fa5adaaeb9e4a3c11566fbf6aaee0f94aa279d6c1332abb802b0591b25944fbefbab03c85c93f6c96c361be94036a6a225410022500ddc0bb704153168dff4003703370c7bdae0fe6f70da3521bfa06a5fc1c46a6bdd09d4d7baf9d4d5c36a97786e53869c8a6be1b54c9a77a97f0685376f854d7b70297b568534c3c54d5bef29a756452feed6a5ed5b7f9d60f0d84700113bf588ca47e561cc5804f89c1082e7f6496df697e9403ca6a22541002ca806ee36e5c0bea62d1bff9dacb7b8b84842d695b05443c86aa6f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9u%7E*t%28750g3%7E1-13a3ef29997-0x16d" + }, + { + "last-modified": "Fri, 05 Oct 2012 05:17:21 GMT" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "60127" + }, + { + "cache-control": "max-age=29262216" + }, + { + "expires": "Tue, 08 Oct 2013 05:56:19 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 236, + "wire": "88ec6c96df697e940814cb6d0a080112820dc13d71a714c5a37fda0f0d836df799588ca47e561cc5804cbacbcf89cf6496d07abe94036a436cca080165403b71a0dc13ea62d1bf6196dc34fd280654d27eea0801128166e322b8d32a62d1bfdf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Tue, 10 Jul 2012 21:28:46 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5983" + }, + { + "cache-control": "max-age=23738926" + }, + { + "expires": "Mon, 05 Aug 2013 07:41:29 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 237, + "wire": "88f0f16c96dc34fd282754d444a820044a05ab8d86e32d298b46ffde0f0d84085e7dbf588ca47e561cc58190b610000dff6496dd6d5f4a0195349fba820059500e5c0bf704f298b46fc1e2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Sat, 27 Oct 2012 14:51:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "11895" + }, + { + "cache-control": "max-age=31510005" + }, + { + "expires": "Sun, 03 Nov 2013 06:19:28 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 238, + "wire": "88f36c96d07abe941094d444a820044a01eb8d39700da98b46ffe10f0d8375c75a588ca47e561cc5819089a6dd13bf6496df3dbf4a321535112a080165403571b6ae36053168dfc4e5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 22 Oct 2012 08:46:05 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7674" + }, + { + "cache-control": "max-age=31245727" + }, + { + "expires": "Thu, 31 Oct 2013 04:54:50 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 239, + "wire": "88f66c96e4593e9403ca436cca080112817ae08171a714c5a37fe40f0d84089d69af588ca47e561cc5804e32f3cf019f6496e4593e94034a6e2d6a080165413371a72e01c53168dfc7e8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Wed, 08 Aug 2012 18:20:46 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "12744" + }, + { + "cache-control": "max-age=26388803" + }, + { + "expires": "Wed, 04 Sep 2013 23:46:06 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 240, + "wire": "88f96c96d07abe9413aa436cca080112817ee32f5c69953168dfe70f0d836de6c5588ca47e561cc5804eb2f34d362f6496d07abe940b8a6e2d6a080165408ae081702da98b46ffcaeb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Mon, 27 Aug 2012 19:38:43 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5852" + }, + { + "cache-control": "max-age=27384452" + }, + { + "expires": "Mon, 16 Sep 2013 12:20:15 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 241, + "wire": "88768c86b19272ad78fe8e92b015c354012a6c96df697e941094d27eea08010a817ae322b80754c5a37fec0f0d83700173588ca47e561cc58190b606da685f6496dd6d5f4a0195349fba820059500ddc033704da98b46fcff0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Tue, 22 Nov 2011 18:32:07 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6016" + }, + { + "cache-control": "max-age=31505442" + }, + { + "expires": "Sun, 03 Nov 2013 05:03:25 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 242, + "wire": "88c26c96dd6d5f4a09953716b50400894102e09db807d4c5a37fef0f0d8379d683588ca47e561cc5804f044f01f0ff6496e4593e94136a6e2d6a080165400ae36d5c0b4a62d1bfd2f3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Sun, 23 Sep 2012 20:27:09 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "8741" + }, + { + "cache-control": "max-age=28128091" + }, + { + "expires": "Wed, 25 Sep 2013 02:54:14 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 243, + "wire": "88c5c46c96df3dbf4a09b535112a080112817ae36cdc13ca62d1bff20f0d836dd13b588ca47e561cc58190b6013a267f6496dd6d5f4a0195349fba820059500d5c0bd700e298b46fd5f6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Thu, 25 Oct 2012 18:53:28 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5727" + }, + { + "cache-control": "max-age=31502723" + }, + { + "expires": "Sun, 03 Nov 2013 04:18:06 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 244, + "wire": "88c8e76c96df697e940b8a6a225410022502fdc64571a714c5a37f5f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ec938ec4153070df8567bf0f0d846da682f7588ca47e561cc5819000026997ff6496e4593e940b8a6a22541002ca817ee32cdc1094c5a37fd9fadd", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Tue, 16 Oct 2012 19:32:46 GMT" + }, + { + "content-type": "application/x-javascript;charset=UTF-8" + }, + { + "content-length": "54418" + }, + { + "cache-control": "max-age=30002439" + }, + { + "expires": "Wed, 16 Oct 2013 19:33:22 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 245, + "wire": "886196dc34fd280654d27eea0801128166e322b8d34a62d1bf6c96df697e941094d03f4a080112817ee00571b654c5a37f5f87352398ac5754dff0ef0f0d83109e7b", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:44 GMT" + }, + { + "last-modified": "Tue, 22 May 2012 19:02:53 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "2288" + } + ] + }, + { + "seqno": 246, + "wire": "88c06497dd6d5f4a09b5349fba820044a099b8d3971b714c5a37ff6c96e4593e94642a436cca08010a8005c65fb816d4c5a37fc0f2f1588ba47e561cc581979e7800070f0d836dd699", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:44 GMT" + }, + { + "expires": "Sun, 25 Nov 2012 23:46:56 GMT" + }, + { + "last-modified": "Wed, 31 Aug 2011 00:39:15 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "cache-control": "max-age=3888000" + }, + { + "content-length": "5743" + } + ] + }, + { + "seqno": 247, + "wire": "88d26c96dd6d5f4a05b532db42820044a01bb8272e09d53168df5f88352398ac74acb37f0f0d83702eb5588ca47e561cc58040700e05e0ff6496dd6d5f4a320532db528200595001b827ee01b53168dfc7408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Sun, 15 Jul 2012 05:26:27 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6174" + }, + { + "cache-control": "max-age=20606181" + }, + { + "expires": "Sun, 30 Jun 2013 01:29:05 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:44 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 248, + "wire": "88d76c96dd6d5f4a09e535112a0801128205c0b371a0298b46ffc20f0d83782f0b588ca47e561cc581903c0138eb5f6496dc34fd282714d444a8200595001b8d82e32f298b46ffcbc1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "last-modified": "Sun, 28 Oct 2012 20:13:40 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "8182" + }, + { + "cache-control": "max-age=30802674" + }, + { + "expires": "Sat, 26 Oct 2013 01:50:38 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:44 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 249, + "wire": "88da7f31a3adaaeb9e4a3c11566fbf6aaee0f94aa279d6c1301c6d60b2f8425209c8cab03c85c93f6c96e4593e9403ca436cca080112820dc685702fa98b46fff0c60f0d846de71973588ba47e561cc5804d08217c026496dc34fd2810290db32820059502fdc035704da98b46ffebc5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9u%7E*t%28750g065-13911ec26be-0x16d" + }, + { + "last-modified": "Wed, 08 Aug 2012 21:42:19 GMT" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "58636" + }, + { + "cache-control": "max-age=24211902" + }, + { + "expires": "Sat, 10 Aug 2013 19:04:25 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 250, + "wire": "88cf6c96e4593e94032a6a2254100225042b806ee36053168dffce52848fd24a8f0f1390fe5e034065f216495d689206e38067f9768dd06258741e54ad9326e61c5c1f0f0d847197c0ff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:32:44 GMT" + }, + { + "last-modified": "Wed, 03 Oct 2012 22:05:50 GMT" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"804039cedf74cd1:603\"" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "content-length": "63909" + } + ] + }, + { + "seqno": 251, + "wire": "88e1e06c96d07abe9413ea6a225410022502ddc6deb8d814c5a37fcc0f0d840b2003df588ca47e561cc58190b6cb6f85ff6496dd6d5f4a0195349fba820059502cdc643704253168dff1cb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "*" + }, + { + "last-modified": "Mon, 29 Oct 2012 15:58:50 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "13008" + }, + { + "cache-control": "max-age=31535919" + }, + { + "expires": "Sun, 03 Nov 2013 13:31:22 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 252, + "wire": "88e47f08a7adaa9570165bd25b64a3c11566fbfdd3f29438eaed15c940502c1646471d7d99596560790bf1ff588caec3771a4bf4a547588324e54085aec1cd48ff86a8eb10649cbf5f87352398ac4c697f798624f6d5d4b27f6196dc34fd280654d27eea0801128166e322b8d36a62d1bf0f0d0234320f28faaab31a08d335a6918238ebcf332842c8c036dc7dc68ae34e11c96518c23205b13ae360764fff0935a6918238ebcf364702c8c0300df95c6dc7a30b92cadbe174a56c4eb8d81d93ffcfb52f9e919aa8172c63f4b90f4fda983cd66b0a88375b57d280656d27eeb08016540b371915c69b53168dff6a6b1a67818f4003703370d2acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5ee1b46a437f40d4bf8388d4d7baf9d4d7ba11a9ab86d53743a0ea64d37d4e1a72297b568534c3c54c9a77a9bb7c2a5fc1a14d7b707f3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4n%60rujfudlwc%3D9vt*ts67.4e6f0e0-13ac6793f33-0x19b" + }, + { + "cache-control": "private, no-cache" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 13:32:45 GMT" + }, + { + "content-length": "42" + }, + { + "set-cookie": "npii=btguid/c67883f113a0a56964e646c6ffaa1ac15276507d^cguid/c67885c613a0a0a9f6568b16ff5917ee5276507d^; Domain=.ebay.com; Expires=Sun, 03-Nov-2013 13:32:45 GMT; Path=/" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa ADMa DEVa PSDo PSAa OUR SAMo IND UNI COM NAV INT STA DEM PRE\"" + } + ] + }, + { + "seqno": 253, + "wire": "88eb7f05a5adaaeb9e4a3c11566fbf6aaee0f94aa279d6c1332abb84eb0591b4075d136d8960790b8fff6c96c361be94036a6a225410022500ddc0bf702d298b46ff7f01c7bdae0fe6f70da3521bfa06a5fc1c46a6bdd09d4d7baf9d4d5c36a97786e53869c8a6be1b54c9a77a97f0685376f854d7b70297b568534c3c54d5bef29a756452feed6a5ed5b7f9d80f0d85085b79c6bf588ca47e561cc5804f89e75d6dff6496df697e9403ca6a22541002ca8166e005700253168dff6196dc34fd280654d27eea0801128166e322b8d32a62d1bfd8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "rlogid": "p4pphdlwc%3D9u%7E*t%28750g3%7Fo-13a40772552-0x169" + }, + { + "last-modified": "Fri, 05 Oct 2012 05:19:14 GMT" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "115864" + }, + { + "cache-control": "max-age=29287759" + }, + { + "expires": "Tue, 08 Oct 2013 13:02:02 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:32:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 254, + "wire": "880f0d8313a067e06c96e4593e9413ca681d8a0801128215c641702ca98b46ffd0cfe36496df697e94640a6a225410022502fdc13b704fa98b46ffda", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "2703" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Wed, 28 Mar 2012 22:30:13 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/6.0" + }, + { + "date": "Sat, 03 Nov 2012 13:32:44 GMT" + }, + { + "expires": "Tue, 30 Oct 2012 19:27:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 255, + "wire": "88f30f28bca2d275f4fc017db7de7c1f6a5f3d2335502e58c7e9721e9fb53079acd615106f9edfa50025b49fbac2005d502cdc64571b794c5a37fda9ac699e063f5885aec3771a4bcb5a839bd9ab5f95497ca58e83ee3412c3569fb24e3b1054c1c37e159e0f0d84780071af6196dc34fd280654d27eea0801128166e322b8dbca62d1bf6c96d07abe941094d444a820044a099b806ae044a62d1bff40884d83a903224c7abfcab7aaf67fb700ec7ffdebff3eb692993248a6135521aa9b5d8792d227bb5f5b683cd92452b6aba4de15701d131beab386deabd8a8aeb8076355dc1d5576f2c1646471d7dc9647160792077f0ca8adab557009474966eb5ca6b65559c2ab3793d9513ddbb2f22ae01b995700db2b059191c75f6597245b842d4b70ddd0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lucky9=1959890; Domain=.ebay.com; Expires=Thu, 02-Nov-2017 13:32:58 GMT; Path=/" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "text/javascript;charset=UTF-8" + }, + { + "content-length": "80064" + }, + { + "date": "Sat, 03 Nov 2012 13:32:58 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 23:04:12 GMT" + }, + { + "transaction": "uk.r+607b~k|,RcmdId FindingProductv4,RlogId p4pmiw%60jtb9%3Fuk.r%2B607b%7Ek%7C-13ac6796fd6-0xc1" + }, + { + "rlogid": "p4u%60tsjfgkpfiuf%3F%3Ctq%28qq.d%605g%6053-13ac679336d" + }, + { + "content-language": "en-US" + }, + { + "transfer-encoding": "chunked" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_26.json b/http/http-client/src/test/resources/hpack-test-case/story_26.json new file mode 100644 index 000000000..24eb00ef8 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_26.json @@ -0,0 +1,4673 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "8854012a0f0d826c425f87352398ac4c697f6c96df3dbf4a044a435d8a0801128066e019b820298b46ff4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb4088f2b4b1ad2163b66fa4c9c4ac6ef16932db7519d3c71f2cbe05af449ab7eaf36e0e5c0d63669b669df3f5df341f4087f2b12a291263d5842507417f5892aed8e8313e94a47e561cc5802e882e3ce3ff6496df697e941054d03f4a08016540bf702f5c65953168df6196dc34fd280654d27eea0801128115c6c171a694c5a37f408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "522" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 12 Apr 2012 03:03:20 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "IVe/SwucJuBsLtVHWJw2PMdOTOxuEWUir5igQNThkTg=" + }, + { + "x-cnection": "close" + }, + { + "cache-control": "public, max-age=17216869" + }, + { + "expires": "Tue, 21 May 2013 19:18:33 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:44 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 1, + "wire": "88c75f91497ca582211f6a1271d882a60b532acf7f6c96df697e94134a435d8a0801128215c0b37196d4c5a37fc67f06a37db4f0f54c83930e791ebf5d343dc6ad47e189dcd3991abc7575acd2303c98a5e0083fc57b8b84842d695b05443c86aa6f5a839bd9ab0f0d8208995892aed8e8313e94a47e561cc5802e32fb4f841f6496dd6d5f4a044a681fa50400b2a01cb8dbf702d298b46fc6c5", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "text/css; charset=utf-8" + }, + { + "last-modified": "Tue, 24 Apr 2012 22:13:35 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "95tUymdadFLd8Dpml8VnOoUG7KhisOwk74Kd/aIGfU0=" + }, + { + "x-cnection": "close" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "123" + }, + { + "cache-control": "public, max-age=16394910" + }, + { + "expires": "Sun, 12 May 2013 06:59:14 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:44 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 2, + "wire": "88cec46c96dd6d5f4a09e535112a080112820dc65db8cb6a62d1bfccc17f04a2d8406227037833263bc3ddb7249f831cfcec733669eb9fbf1734d1a5eee7623bed410f0d840b4e3cd75892aed8e8313e94a47e561cc5819081c75c65bf6496df697e9413ea6a22541002ca8015c69ab8cbea62d1bfcac9c5", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "text/css; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 21:37:35 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "Qc0GcUiwi3io8aSRIdXaahYr6KKhphvV6NlN8vo/bD4=" + }, + { + "content-length": "14684" + }, + { + "cache-control": "public, max-age=31067635" + }, + { + "expires": "Tue, 29 Oct 2013 02:44:39 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:44 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 3, + "wire": "88d25f87352398ac5754df6c96df3dbf4a044a435d8a0801128066e019b82654c5a37fd17f03a474be7876ea7fdf29973a0bb43ef3f9cb47e555f3cce68d479aafdb6f66f2ec9649b4f07f0f0d840b4d32f75892aed8e8313e94a47e561cc5804d3e271b701f6496d07abe940bea436cca0801654002e36cdc134a62d1bfcfce", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Thu, 12 Apr 2012 03:03:23 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "7exUqkoZxtfLseR1zLxJlXnpYK6MOognZuCKx7drdRo=" + }, + { + "content-length": "14438" + }, + { + "cache-control": "public, max-age=24926560" + }, + { + "expires": "Mon, 19 Aug 2013 00:53:24 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:44 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 4, + "wire": "88d75f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ed424e3b1054c16a6559ef6c96d07abe9413ea6a225410022502edc03d71b714c5a37fd6cb7f03a62edefeb2e7fcc9df933c6d7e4ff74b4cbfdffde7c59bb7e1b3eecd4fb77c3ec79d10f0ac907f0f0d840bad3adf5892aed8e8313e94a47e561cc58190844d34277f6496df697e9413ea6a22541002ca817ae321b810a98b46ffd4d3cf", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Mon, 29 Oct 2012 17:08:56 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "eRvyJLXIvW3Vu9d+m439v+LGKqXiLSKmz7w9/xMAUpc=" + }, + { + "content-length": "17475" + }, + { + "cache-control": "public, max-age=31124427" + }, + { + "expires": "Tue, 29 Oct 2013 18:31:11 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:44 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 5, + "wire": "88dcd26c96dd6d5f4a09e535112a0801128215c6dbb82754c5a37fdacf7f02a40b539f013d78d3a745dc9c786e6eebb0df776dfc46bfdf2b5771175719b2c776ffb941070f0d8469a0be1fcbcad6d5d1", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "text/css; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 22:55:27 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "14hoEcywNNMBIVUS5B7AD7RDGiDvJ4BGeOVgJbBDzf0=" + }, + { + "content-length": "44191" + }, + { + "cache-control": "public, max-age=31067635" + }, + { + "expires": "Tue, 29 Oct 2013 02:44:39 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:44 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 6, + "wire": "88dec46c96c361be940894d444a820044a05eb8cb571a794c5a37fdc7f00a3682c63b71130e9d14fa4344ef8b35174bea83f4938dfd6d7fbe37724a198d6b25d3b200f0d033735345892aed8e8313e94a47e561cc5804fbef361705f6496e4593e940b8a6a22541002ca816ae019b8c854c5a37f6196dc34fd280654d27eea0801128115c6c171a7d4c5a37fdad5d6", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Fri, 12 Oct 2012 18:34:48 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "41/HuGcFNMmys4cvGKlBeylojdVDP4+VBIf1giu3eNQ=" + }, + { + "content-length": "754" + }, + { + "cache-control": "public, max-age=29985162" + }, + { + "expires": "Wed, 16 Oct 2013 14:03:31 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:49 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 7, + "wire": "88e3ce6c96df3dbf4a09b535112a0801128172e01bb8db2a62d1bfe17f03a53e6d9e3b832e7e67427fdfbed4777bcffbbcee8c19ddf7b6ec65d07a4e46dad0dedfde707fe0d8d70f0d83132d3f5892aed8e8313e94a47e561cc5819081c759703f6496df697e9413ea6a22541002ca8015c681702053168dffc2de", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Thu, 25 Oct 2012 16:05:53 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "oKQwv0JLYost+zqlv8x+C7MEL7zRBbeMomoc54M5RZY=" + }, + { + "x-cnection": "close" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "2349" + }, + { + "cache-control": "public, max-age=31067361" + }, + { + "expires": "Tue, 29 Oct 2013 02:40:10 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:49 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 8, + "wire": "88e70f0d83138273cd6c96dd6d5f4a09e535112a080112820dc03d71b0298b46ffe57f02a4f73bc9db8f3614e0db93f7fde660b88da036a690e371cd556ae81ede35c3a5dbdd5f860fe45892aed8e8313e94a47e561cc5819081c79b741f6496df697e9413ea6a22541002ca8066e001702fa98b46ffc6e2ddde", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "2626" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 21:08:50 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "zh8tRHKFtERIZ+K/eGiM1utm1H66OnOj1qwPAN7Ck9A=" + }, + { + "x-cnection": "close" + }, + { + "cache-control": "public, max-age=31068570" + }, + { + "expires": "Tue, 29 Oct 2013 03:00:19 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:49 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 9, + "wire": "88ebd66c96c361be9413ca6e2d6a080112816ee003702d298b46ffe9de7f02a2aec313b6424f8fda71f9cd0ecdc69361bc62a76cae0bbc39dcc8eea302c633180f410f0d83780cb95892aed8e8313e94a47e561cc5819081c71f681f6496df697e9413ea6a22541002ca8015c659b807d4c5a37fcae6e2", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Fri, 28 Sep 2012 15:01:14 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "pricqIchHztHxKAQSidQiwGmRf62vAL6I7Oi0r/Ki08=" + }, + { + "content-length": "8036" + }, + { + "cache-control": "public, max-age=31066940" + }, + { + "expires": "Tue, 29 Oct 2013 02:33:09 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:49 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 10, + "wire": "88efd56c96dc34fd282754d444a820044a08371a15c0bea62d1bffede27f02a5cd6d82c3c386ce90ec07ad7b32de7e5ef99b718ff79f97ee3efeb66172f14586d1ca2eb07f0f0d8465c6402f5892aed8e8313e94a47e561cc5819081c79a741f6496df697e9413ea6a22541002ca8015c6deb8cbea62d1bfceeae6", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Sat, 27 Oct 2012 21:42:19 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "Kur2FUUQjAQ0yPQJC9fvK56/+LWZHvyQF6Ce2Fuaf2k=" + }, + { + "content-length": "36302" + }, + { + "cache-control": "public, max-age=31068470" + }, + { + "expires": "Tue, 29 Oct 2013 02:58:39 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:49 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 11, + "wire": "88f3de6c96df3dbf4a044a435d8a0801128066e00571b0a98b46fff1e67f02a3f1d45a397a4659fc33812ededb8b44214607f1f2b7d7bf4f1ef774ef17174daff0b3410f0d83784c835892aed8e8313e94a47e561cc5804db8cbee3eff6496df697e9413aa436cca080165403971b7ee01e53168dfd2eeea", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Thu, 12 Apr 2012 03:02:51 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "wk2MWysJhw3Et7CRGMA1sE9HWuyzy8oCvtT2V7iPXeg=" + }, + { + "content-length": "8230" + }, + { + "cache-control": "public, max-age=25639699" + }, + { + "expires": "Tue, 27 Aug 2013 06:59:08 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:49 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 12, + "wire": "8854012ade6c96dc34fd282754d444a820044a0837197ae34d298b46fff6eb7f03a4c5a3dfbf5d0175e7f3f3943ef3637e7ede7f4bd7db74f3df4a2df3e33db372f441629a0f0f0d8369e75e5892aed8e8313e94a47e561cc5819081c79b13ff6496df697e9413ea6a22541002ca8015c6dfb8cbca62d1bfd7f3ef", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Sat, 27 Oct 2012 21:38:44 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "GMzzyj0B89LYf1zKH9hqxZekz5mYTmsuxwLugWyc2Gg=" + }, + { + "content-length": "4878" + }, + { + "cache-control": "public, max-age=31068529" + }, + { + "expires": "Tue, 29 Oct 2013 02:59:38 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:49 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 13, + "wire": "886c96dc34fd280654d27eea080112806ae32edc69e53168df6496dc34fd281029a4fdd410022500d5c65db8d3ca62d1bf5f911d75d0620d263d4c1c88ad6b0a8acf520b409221ea496a4ac9b0752252d8b16a21e435537f858cd50ecf5f0f0d830bc16b58a7a47e561cc581b75b105bfa52bb63a0c4fa52a3ac9b0752253d94fd294da84ad617b8e83483497f6196dc34fd280654d27eea0801128115c6c171b654c5a37f4087aaa21ca4498f57842507417f408721eaa8a4498f5788cc52d6b4341bb97f", + "headers": [ + { + ":status": "200" + }, + { + "last-modified": "Sat, 03 Nov 2012 04:37:48 GMT" + }, + { + "expires": "Sat, 10 Nov 2012 04:37:48 GMT" + }, + { + "content-type": "application/ocsp-response" + }, + { + "content-transfer-encoding": "binary" + }, + { + "content-length": "1814" + }, + { + "cache-control": "max-age=575215, public, no-transform, must-revalidate" + }, + { + "date": "Sat, 03 Nov 2012 12:50:53 GMT" + }, + { + "nncoection": "close" + }, + { + "connection": "Keep-Alive" + } + ] + }, + { + "seqno": 14, + "wire": "8858bbaec3771a4bf4a54759093d85fa52a3ac419272fd294da84ad617b8e83483497e94ace84ac49ca4eb003e94aec2ac49ca4eb003e94a47e561cc58015f87352398ac4c697f6495df3dbf4a05486bb141000d2800dc006e002a62d1bf6c95df3dbf4a05486bb141000d2800dc006e000a62d1bf4085aec1cd48ff86a8eb10649cbf4089f2b4b1ad495361888f1c7b2277223a35332c2272223a32362c2271223a302c2261223a32357d4089f2b4b1ac82d9dcb67f88081775a5de7d71337f10a474d9fa23671fc45b570cdf85674d1c45e923bb8bdec071c77bae8fc632b9660b6eb9ce0f6196dc34fd280654d27eea0801128115c6c171b694c5a37f7f0888ea52d6b0e83772ff0f0d023433", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0" + }, + { + "content-type": "image/gif" + }, + { + "expires": "Thu, 1 Apr 2004 01:01:01 GMT" + }, + { + "last-modified": "Thu, 1 Apr 2004 01:01:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-fb-metrics": "{\"w\":53,\"r\":26,\"q\":0,\"a\":25}" + }, + { + "x-fb-server": "10.74.89.23" + }, + { + "x-fb-debug": "7iLjsQVXsunUKXe3NlV2ytaBGzQ0VHCkMX/J6rEuB6Y=" + }, + { + "date": "Sat, 03 Nov 2012 12:50:54 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "43" + } + ] + }, + { + "seqno": 15, + "wire": "88d45f91497ca582211f6a1271d882a60b532acf7f6c96dd6d5f4a09e535112a080112816ee01cb8db2a62d1bf4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb5a839bd9ab7f05a4b6cff7bf1d18719739d28a6fd1b3973d7e058b6e0cfebbcc27b2d3801979f14b6e5a783f0f0d83085b6f5892aed8e8313e94a47e561cc5819081c75d0b5f6496df697e9413ea6a22541002ca8015c69cb80794c5a37fc6c57b8b84842d695b05443c86aa6f", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "text/css; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 15:06:53 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "ur+THlFHeLotsmDlQWYPw2GRELyvg28JmE0JYVt56uo=" + }, + { + "content-length": "1155" + }, + { + "cache-control": "public, max-age=31067714" + }, + { + "expires": "Tue, 29 Oct 2013 02:46:08 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:54 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 16, + "wire": "88dcc56c96c361be94642a436cca0801128215c0b3704f298b46ffc47f03a471897363ebb5376bded1fb5d999653871f5157a9cdd7ff68e18821e1d9a621a3864c107f0f0d033531365892aed8e8313e94a47e561cc5804e321719035f6496e4593e94034a6e2d6a080165400ae36ddc6de53168dfcbcac6c2", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "text/css; charset=utf-8" + }, + { + "last-modified": "Fri, 31 Aug 2012 22:13:28 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "6/fKHkRtBpT4oqBg33tFHk2pO6SDZlUG11Uq4/AlUIE=" + }, + { + "content-length": "516" + }, + { + "cache-control": "public, max-age=26316304" + }, + { + "expires": "Wed, 04 Sep 2013 02:55:58 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:54 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 17, + "wire": "88e00f0d8213225f87352398ac5754df6c96dc34fd2820a90d762820044a01db8066e36ea98b46ffc97f03a4fc4ef46ad25c58f12180f7dbb7bbc4efa798486bacfe76a2cef56fec2c5fbf9deaeda20f4087f2b12a291263d5842507417f5892aed8e8313e94a47e561cc581903417dc0bff6496d07abe941054d444a820059502d5c69ab8cb6a62d1bf6196dc34fd280654d27eea0801128115c6c171b714c5a37fd1cdc9", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "232" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Sat, 21 Apr 2012 07:03:57 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "XtTsONeGHGs/1vRRv8cvNY1ciB3XqlrvnTq2GZXvnqM=" + }, + { + "x-cnection": "close" + }, + { + "cache-control": "public, max-age=30419619" + }, + { + "expires": "Mon, 21 Oct 2013 14:44:35 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:56 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 18, + "wire": "88e7c46c96c361be9403aa6e2d6a080112816ee05eb8d014c5a37fcf7f04a5c02cb779ef1d7ebf30f4f3bf7f1edd6c2f5dfc735fac345fd9b37b0ef3f24f32e9e02e107fc3cbcf0f0d8268005892aed8e8313e94a47e561cc5819081c71f659ff3c1d4", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Fri, 07 Sep 2012 15:18:40 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "E2JBYTapyXFjxTTVqkrekTVKDp1lDQQT/7YxcxfNU2U=" + }, + { + "x-cnection": "close" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "400" + }, + { + "cache-control": "public, max-age=31066933" + }, + { + "expires": "Tue, 29 Oct 2013 02:33:09 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:56 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 19, + "wire": "88ea5f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ed424e3b1054c16a6559ef6c96dc34fd282754d444a820044a08371a0dc69b53168dffd3d27f02a336b8f3c9d075ecf7b4e34c7ab85fb34ffb2f9bfd1ec1af1e58488fd69eaf8a6bb130c10f0d8465b75c735892aed8e8313e94a47e561cc5819081c79f781f6496df697e9413ea6a22541002ca8066e01db81694c5a37fdad9d1", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Sat, 27 Oct 2012 21:41:45 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "iPbLdjapQzRoatbOUDrN+exDj8EPHJAcsZ48pVtprtA=" + }, + { + "content-length": "35766" + }, + { + "cache-control": "public, max-age=31068980" + }, + { + "expires": "Tue, 29 Oct 2013 03:07:14 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:54 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 20, + "wire": "88efcc6c96dd6d5f4a09e535112a080112816ae32d5c6c0a62d1bfd77f02a4010c0daded11bbe4e37b6d9e1389b03b80bd3fde7df99e938677a4c86fdadd07fb93841f0f0d84081f00bf5892aed8e8313e94a47e561cc5819081c71f71bf6496df697e9413ea6a22541002ca8015c659b8d014c5a37f6196dc34fd280654d27eea0801128115c6c171b6d4c5a37fdedad6", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Sun, 28 Oct 2012 14:34:50 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "0ci0R5R2ivIVCRrwtG507Eej+LTK8dUL8dIiZp70+dU=" + }, + { + "content-length": "10902" + }, + { + "cache-control": "public, max-age=31066965" + }, + { + "expires": "Tue, 29 Oct 2013 02:33:40 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:55 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 21, + "wire": "88f4dd6c96dd6d5f4a09e535112a080112820dc65eb82794c5a37fdcdb7f03a5d62bbd8058f8fe744caf62f5b2b0e3c76f97b4f9a254f0d1f9fb6f37bf30ffbf43d5c4f07f0f0d8369c75c5892aed8e8313e94a47e561cc5819081e79d13df6496df697e9413ea6a22541002ca807ae32e5c134a62d1bfcfe2da", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "text/css; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 21:38:28 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "P2Bq0ebVXjtf8GyQp1HHux8NxlftUMXZuY8XF+yaOVo=" + }, + { + "content-length": "4676" + }, + { + "cache-control": "public, max-age=31088728" + }, + { + "expires": "Tue, 29 Oct 2013 08:36:24 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:56 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 22, + "wire": "8854012ae26c96dd6d5f4a09e535112a080112820dc65db810298b46ffe1e07f03a5f58fdf16080f0cfefac9575db1e7bd7ec84096c726ed1ea1c4d5765dd5d7fd9bfda5ce707f0f0d84101d7c3f5892aed8e8313e94a47e561cc5819081c75c6daf6496df697e9413ea6a22541002ca8015c69bb810298b46ffd4e7df", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "text/css; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 21:37:10 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "yHzV/c0w3ZyInkRbLCDrA0t5adSMyAG4prBOk+i+t6Y=" + }, + { + "content-length": "20791" + }, + { + "cache-control": "public, max-age=31067654" + }, + { + "expires": "Tue, 29 Oct 2013 02:45:10 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:56 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 23, + "wire": "88c20f0d840842167fda6c96dd6d5f4a09e535112a080112816ae32d5c69d53168dfe57f02a4b59719d5df361a6aa5d0bb96ef0fc85b2ebbaf6f402baf6e477fd97065f1ab516b73c41fd95892aed8e8313e94a47e561cc5819081c71f79ef6496df697e9413ea6a22541002ca8015c65ab80694c5a37fd8ebe7e3", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "11113" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Sun, 28 Oct 2012 14:34:47 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "u363OvKFmnm717JBUXA5ePB8Ts0ppRI7+eEJwOOep6w=" + }, + { + "x-cnection": "close" + }, + { + "cache-control": "public, max-age=31066988" + }, + { + "expires": "Tue, 29 Oct 2013 02:34:04 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:56 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 24, + "wire": "88c6f36c96df3dbf4a044a435d8a0801128066e00571b754c5a37fe9e87f02a4e69f5fef795ad5ee54784d3d3dc7969cbfa3f51fad009fcfe1b6f01694e3c11dfb75e0830f0d0234335892aed8e8313e94a47e561cc5804dbadb2dbacf6496e4593e9413ca436cca08016540b571976e01f53168dfdcefe7", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 12 Apr 2012 03:02:57 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "Yty+Te4OzfswtmjzbJmJZaybyM0hxXiRU2NtHEbDuPE=" + }, + { + "content-length": "43" + }, + { + "cache-control": "public, max-age=25753573" + }, + { + "expires": "Wed, 28 Aug 2013 14:37:09 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:56 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 25, + "wire": "88cae26c96df3dbf4a044a435d8a0801128066e019b82694c5a37fedec7f02a3f59b0fab3cf8ed74d6d3a35cbcbbf76a93d9c38229f6fc9d3f53ba4a6f1f668a893a200f0d033537315892aed8e8313e94a47e561cc5804dbadb2f043f6496e4593e9413ca436cca08016540b571a0dc03aa62d1bfe0f3eb", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Thu, 12 Apr 2012 03:03:24 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "yKFyrxwqBiumMPfWvv4morUUsmz9djZtSdmCoQMnchs=" + }, + { + "content-length": "571" + }, + { + "cache-control": "public, max-age=25753811" + }, + { + "expires": "Wed, 28 Aug 2013 14:41:07 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:56 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 26, + "wire": "88ce5f87352398ac4c697f6c96df3dbf4a044a435d8a0801128066e019b821298b46fff27f03a4c71d9ebcd5addc7a46ef5937de9e172ed75eee99f67804dedffdf78375eed831b2c3fd600f0d0234335892aed8e8313e94a47e561cc5804dbadb2eba0f6496e4593e9413ca436cca08016540b571a05c138a62d1bfe5408721eaa8a4498f5788ea52d6b0e83772fff5f1", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 12 Apr 2012 03:03:22 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "HbryxnP7HNa7kdTChA6BppSjLQw0gz9ZzESCqEH3/9k=" + }, + { + "content-length": "43" + }, + { + "cache-control": "public, max-age=25753770" + }, + { + "expires": "Wed, 28 Aug 2013 14:40:26 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:56 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 27, + "wire": "88d4ec6c96df697e9413ca436cca080112800dc102e000a62d1bff4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb5a839bd9ab7f05a12c937692d0d1869e1c9916048a7c673428adfeb8721277b65f310da9d093635e200f0d84089e0bbf5892aed8e8313e94a47e561cc5804e34dbc0683f6496df3dbf4a01b53716b50400b2a05eb817ae05d53168dfecc47b8b84842d695b05443c86aa6f", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Tue, 28 Aug 2012 01:20:00 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "edgqdu1lFmUW32Et2hHoiAsp9kFIch8QDiciO71cQ4w=" + }, + { + "content-length": "12817" + }, + { + "cache-control": "public, max-age=26458041" + }, + { + "expires": "Thu, 05 Sep 2013 18:18:17 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:56 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 28, + "wire": "88dbe96c96dd6d5f4a09e535112a080112820dc03f7190298b46ffc4c37f03a5b3f3f13e72332eb9b4e32b9e3364bfb6eed079e894f0d25c5feeedfd9a1ed92e6d83ff70c10f0d84085c79ef5892aed8e8313e94a47e561cc5819081c79f7c1f6496df697e9413ea6a22541002ca8066e01db82754c5a37f6196dc34fd280654d27eea0801128115c6c171b754c5a37fcac3", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 21:09:30 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "rXXtxI3fPgNHe6wKIDRBR0xjttUNeG+BDQM8QfKQa+A=" + }, + { + "content-length": "11688" + }, + { + "cache-control": "public, max-age=31068990" + }, + { + "expires": "Tue, 29 Oct 2013 03:07:27 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:57 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 29, + "wire": "88e00f0d8465b0b8dfee6c96dd6d5f4a09e535112a080112820dc03971a754c5a37fc97f03a49786516923fdb2c976f061ab9e185be280cae9fdf7ff66635efcdabfdf12d608418f641f4087f2b12a291263d5842507417f5892aed8e8313e94a47e561cc5819081c79b107f6496df697e9413ea6a22541002ca8015c6dfb8cbca62d1bfc3cfccc8", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "35165" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 21:06:47 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "fUJ2Nc9qJdBC1AnYFA5Vs1f7ozv+i/PTKO+Vep0A0HQ=" + }, + { + "x-cnection": "close" + }, + { + "cache-control": "public, max-age=31068521" + }, + { + "expires": "Tue, 29 Oct 2013 02:59:38 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:57 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 30, + "wire": "88e5f36c96dd6d5f4a09e535112a080112820dc03b700053168dffcecd7f03a443ffb4e5cd573c5460e99311def3b3df9a27e3a1aa8d462c7313b8659f307f5dc21f107f0f0d830804f75892aed8e8313e94a47e561cc5819085f0bcd3ff6496e4593e94640a6a22541002ca8166e05bb80754c5a37f6196dc34fd280654d27eea0801128115c6c171b794c5a37fd4cd", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 21:07:00 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "s9ZmJKnYGlEjIGo8xQzxlhVM4nilGHgcv1fhK1Z7F1w=" + }, + { + "content-length": "1028" + }, + { + "cache-control": "public, max-age=31191849" + }, + { + "expires": "Wed, 30 Oct 2013 13:15:07 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:58 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 31, + "wire": "88ea5f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ed424e3b1054c16a6559ef6c96dd6d5f4a09e535112a080112820dc03b700f298b46ffd4d37f04a39f46cef0bbe67b6145696e5d5e4b25809797bd9fba73f5f46f7c534528afdcdc713d070f0d8469d79a6bcdcccbd7d0", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 21:07:08 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "hMQvA7xhuAspt5fOxedr0fWzQZNLkyizVtlmspzgVG8=" + }, + { + "content-length": "47844" + }, + { + "cache-control": "public, max-age=31068990" + }, + { + "expires": "Tue, 29 Oct 2013 03:07:27 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:57 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 32, + "wire": "88edc06c96d07abe9413ea6a225410022502fdc0bb704d298b46ffd67f00a40e0eb97373cf2f2b483bfc67fdbd838c309db771d70e376cef85ab1fc9e2ffe6815d7a0f0f0d84684cbe1f5892aed8e8313e94a47e561cc5819085a65e645f6496df697e9413ea6a22541002ca8266e36d5c69f53168dfcfdbd8d4", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Mon, 29 Oct 2012 19:17:24 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "1EkJKYLfWucaDVhZCEVAAo57HpAH7rvF4r9IwDXM2B8=" + }, + { + "content-length": "42391" + }, + { + "cache-control": "public, max-age=31143832" + }, + { + "expires": "Tue, 29 Oct 2013 23:54:49 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:57 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 33, + "wire": "88f10f0d840b21783fc46c96dd6d5f4a09e535112a080112820dc082e32253168dffda7f02a493539ab3709597ba5f9130d5becd027bc7eefbbedfe6dcfc18060cb2481bdd7962be883fce5892aed8e8313e94a47e561cc5819081c79f79ffd3c8dedbd7", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "13181" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 21:10:32 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "dO6OKUf38jDdtAnTrM28wZTBz9Y5hU/0EJdd1CkWGDs=" + }, + { + "x-cnection": "close" + }, + { + "cache-control": "public, max-age=31068989" + }, + { + "expires": "Tue, 29 Oct 2013 03:07:27 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:58 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 34, + "wire": "88f4c76c96c361be940094d27eea080112817ee019b8dbaa62d1bfdddc7f01a4c947eb527a233ddd97b5929874f6cb1e12e1dfcb6f5e1df07040b3bd9b3fe691ffb6cf070f0d84132eb2e75892aed8e8313e94a47e561cc58190b4ebccb80f6496dc34fd280129a4fdd41002ca8205c6c1702ea98b46ffd6e2db", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Fri, 02 Nov 2012 19:03:57 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "IlZ4dyc3v7fqrfiamqJbFeFTWRkUvEUs2L8KLXNa+5o=" + }, + { + "content-length": "23736" + }, + { + "cache-control": "public, max-age=31478360" + }, + { + "expires": "Sat, 02 Nov 2013 20:50:17 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:57 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 35, + "wire": "8854012acc6c96df3dbf4a002a693f750400894102e32ddc13ca62d1bfe2e17f03a3db838fb0756b15b23b1964f7ddfcdbb7e211839b5567b2306f0b54f29d2fb7a326083f0f0d84089f71ff5892aed8e8313e94a47e561cc58190b2fb4f3cdf6496c361be940054d27eea0801654106e32fdc032a62d1bfd1e7e0", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Thu, 01 Nov 2012 20:35:28 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "REVz0k4Gud7bedzv9KSTG2i1KOporb0T14mWht95MIE=" + }, + { + "content-length": "12969" + }, + { + "cache-control": "public, max-age=31394885" + }, + { + "expires": "Fri, 01 Nov 2013 21:39:03 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:58 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 36, + "wire": "88c2d06c96dd6d5f4a09e535112a080112820dc03971a694c5a37fe6e57f02a4e39bbdbcf3b0fd9dbbf4fc9dda6c7b3f4dd96924db33feb03ff7fa9edd043e08d632120f0f0d8379a6dd5892aed8e8313e94a47e561cc5819081e79d13df6496df697e9413ea6a22541002ca807ae32e5c138a62d1bfd5ebe4", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 21:06:44 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "VKvuYL/9rqvjXh7mr8LjSJmcgQLZ/a+Ztqj2aUsPacc=" + }, + { + "content-length": "8457" + }, + { + "cache-control": "public, max-age=31088728" + }, + { + "expires": "Tue, 29 Oct 2013 08:36:26 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:58 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 37, + "wire": "8858bbaec3771a4bf4a54759093d85fa52a3ac419272fd294da84ad617b8e83483497e94ace84ac49ca4eb003e94aec2ac49ca4eb003e94a47e561cc5801f16495df3dbf4a05486bb141000d2800dc006e002a62d1bf6c95df3dbf4a05486bb141000d2800dc006e000a62d1bf4085aec1cd48ff86a8eb10649cbf4089f2b4b1ad495361888f1c7b2277223a32362c2272223a31382c2271223a302c2261223a33307d4089f2b4b1ac82d9dcb67f8908170b8d2ef38bb4ff7f07a32cd96c349c6c06a9e6f0cec8f095c72b7a7dfc58fdbe3ff74489b2ce8db7286ff89a0f6196dc34fd280654d27eea0801128115c6c171b7d4c5a37ff30f0d023433", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0" + }, + { + "content-type": "image/gif" + }, + { + "expires": "Thu, 1 Apr 2004 01:01:01 GMT" + }, + { + "last-modified": "Thu, 1 Apr 2004 01:01:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-fb-metrics": "{\"w\":26,\"r\":18,\"q\":0,\"a\":30}" + }, + { + "x-fb-server": "10.164.86.49" + }, + { + "x-fb-debug": "egJridVr0Ohgw3QbFe66p8hTV/ZDa+ldtrrj55f1Dwg=" + }, + { + "date": "Sat, 03 Nov 2012 12:50:59 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "43" + } + ] + }, + { + "seqno": 38, + "wire": "88c55f87352398ac4c697fc5c4c37f031c7b2277223a31352c2272223a37342c2271223a302c2261223a32317d7f038908170b8d2e2089779b7f03a4772f18f863c47ca4cfef2478e6eb975f705fdd77d13373e7af59ece04019cd526df0f41fc2408721eaa8a4498f5788ea52d6b0e83772ff0f0d023433", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0" + }, + { + "content-type": "image/gif" + }, + { + "expires": "Thu, 1 Apr 2004 01:01:01 GMT" + }, + { + "last-modified": "Thu, 1 Apr 2004 01:01:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-fb-metrics": "{\"w\":15,\"r\":74,\"q\":0,\"a\":21}" + }, + { + "x-fb-server": "10.164.212.85" + }, + { + "x-fb-debug": "7JVbUHGoJcLzIbHgkJPv0DSBycKYYPPorUc0i6OdRw8=" + }, + { + "date": "Sat, 03 Nov 2012 12:50:59 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "43" + } + ] + }, + { + "seqno": 39, + "wire": "88ca5f88352398ac74acb37fcac9c87f031c7b2277223a33332c2272223a31342c2271223a302c2261223a32377d7f038a08170b8d2e2032bbcdff7f03a3dd8f09af3363766bfddc7f5b1c877b47773d9edb78e23009b746d3e73e6cd0f1ce483fc7c20f0d84081965af5a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Thu, 1 Apr 2004 01:01:01 GMT" + }, + { + "last-modified": "Thu, 1 Apr 2004 01:01:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-fb-metrics": "{\"w\":33,\"r\":14,\"q\":0,\"a\":27}" + }, + { + "x-fb-server": "10.164.203.85" + }, + { + "x-fb-debug": "SHFiC3r5rPZSoyQ6AT4o7Lrz58o2i0cRMRoLoKKAVLc=" + }, + { + "date": "Sat, 03 Nov 2012 12:50:59 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "10334" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 40, + "wire": "88cfc2cecdcc7f021c7b2277223a34372c2272223a32372c2271223a302c2261223a32347d7f028908170b8d2e110576db7f02a4baf6fd826f1e7b2be1a3fd8aab672f4ff6ebef6274d18acfc467f39afeda6991c65c6a0fcbc60f0d8475f005ffc1", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Thu, 1 Apr 2004 01:01:01 GMT" + }, + { + "last-modified": "Thu, 1 Apr 2004 01:01:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-fb-metrics": "{\"w\":47,\"r\":27,\"q\":0,\"a\":24}" + }, + { + "x-fb-server": "10.164.121.55" + }, + { + "x-fb-debug": "B8TQ25HLrpUM+2nuhej+798G7ib2rXsLxKDRmmd6364=" + }, + { + "date": "Sat, 03 Nov 2012 12:50:59 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "79019" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 41, + "wire": "88db0f0d023433ca6c96df3dbf4a044a435d8a0801128066e019b806d4c5a37f4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb7f01a3ec1e7469d9867e16643031e179e9b0b0869d3277e8bc0873f1dc2339f8760bbb0ec83ff45892aed8e8313e94a47e561cc5802e882e3cdb9f6496df697e941054d03f4a08016540bf702f5c65b53168dfd0cbc67b8b84842d695b05443c86aa6f", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "43" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 12 Apr 2012 03:03:05 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "q1YlNQFhUrIi0HF88gF/s47itTMC0ALVS2i6Xo/eSFQ=" + }, + { + "x-cnection": "close" + }, + { + "cache-control": "public, max-age=17216856" + }, + { + "expires": "Tue, 21 May 2013 19:18:35 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:59 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 42, + "wire": "88e1d06c96df3dbf4a044a435d8a0801128066e019b820298b46ffc3c87f03a4d5ab3595f32e50e6116de3cb84c09d9fc6b567bfc3fdc7aff219f1945fb1e6a479321e0f0f0d0234335892aed8e8313e94a47e561cc5802e882e3cdb7f6496df697e941054d03f4a08016540bf702f5c65a53168dfd5d0c24087f2b12a291263d5842507417f", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 12 Apr 2012 03:03:20 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "OOKrpYeJ1K2euVWUg0h3X4OLDU+bPXAhHe2ZbKmaIIo=" + }, + { + "content-length": "43" + }, + { + "cache-control": "public, max-age=17216855" + }, + { + "expires": "Tue, 21 May 2013 19:18:34 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:59 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-cnection": "close" + } + ] + }, + { + "seqno": 43, + "wire": "88e6f46c96dc34fd282754d444a820044a08371a15c13ca62d1bffc87f03a4bbcb9cd7b047ecbcbd60db7db3ebd6d8521a717fbf356e075a43639bff6c910fdd939c1f0f0d846db6d907ed6496df697e9413ea6a22541002ca8066e01db82754c5a37f6196dc34fd280654d27eea0801128115c6c171b794c5a37fd5d0c7", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Sat, 27 Oct 2012 21:42:28 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "BWYgCEbzeWyERD5oPP51t1mG+xnS0km1r6TZrds9BdY=" + }, + { + "content-length": "55530" + }, + { + "cache-control": "public, max-age=31068989" + }, + { + "expires": "Tue, 29 Oct 2013 03:07:27 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:58 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 44, + "wire": "88e15f87352398ac5754dfe1e0dfd0cf7f02a472fdfab46e4d536a67011aed8f8e5cf51eddec6ae9cb266e5aa766b77f02fd1e2116083fdcd70f0d023637d2", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0" + }, + { + "content-type": "image/png" + }, + { + "expires": "Thu, 1 Apr 2004 01:01:01 GMT" + }, + { + "last-modified": "Thu, 1 Apr 2004 01:01:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-fb-metrics": "{\"w\":47,\"r\":27,\"q\":0,\"a\":24}" + }, + { + "x-fb-server": "10.164.121.55" + }, + { + "x-fb-debug": "6DDnMStngO3Ec4qHVJLnouT/OjWIKWOh3p7X19lwA2E=" + }, + { + "date": "Sat, 03 Nov 2012 12:50:59 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "67" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 45, + "wire": "88bf7ea5bfcffdc9a9cc28731eae9973fbfc9750bbfb21e1bd6cc7afc27a7fbfd977612b3fec190f07ddd80f0d023637", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "x-fb-debug": "DY+dO6Fs6HOjJLzXfO2vzcoACugopwtj+ZfSFe3+0Io=" + }, + { + "date": "Sat, 03 Nov 2012 12:50:59 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "67" + } + ] + }, + { + "seqno": 46, + "wire": "88edc06c96d07abe940b6a6a2254100225001b817ee09e53168dffcfd47f00a4e0ca5b3f4fd87f3751ed8b254f7e3f9a2437fb9360e1a2f79eefb76b55619291eaaf841f0f0d837196455892aed8e8313e94a47e561cc5804fbce36d3ccf6496df697e940b6a6a22541002ca806ae34fdc0094c5a37fe1dcce", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Mon, 15 Oct 2012 01:19:28 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "U3t5ojZAXSlz/rftvVXMdi+dQaAlCxv95u4nFdmaOpU=" + }, + { + "content-length": "6332" + }, + { + "cache-control": "public, max-age=29865483" + }, + { + "expires": "Tue, 15 Oct 2013 04:49:02 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:50:59 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 47, + "wire": "88f1e06c96df3dbf4a044a435d8a0801128066e019b817d4c5a37fd3d87f02a3e5d74bfd7def0648f2d54ebe30958cb59b507342ca2fd2de5367d36ffbaca3e02160830f0d830804df5892aed8e8313e94a47e561cc5802e882e3cdb5fcd6196dc34fd280654d27eea0801128115c6c3700053168dffe0d2cd", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 12 Apr 2012 03:03:19 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "WkN9kzT0IbJnmPVAe/JpiO1KA3sDm5JiLNu+peaU22E=" + }, + { + "content-length": "1025" + }, + { + "cache-control": "public, max-age=17216854" + }, + { + "expires": "Tue, 21 May 2013 19:18:34 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:00 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-cnection": "close" + } + ] + }, + { + "seqno": 48, + "wire": "88f50f0d8375f0375f91497ca582211f6a1271d882a60b532acf7f6c96dd6d5f4a09e535112a080112820dc65eb8d854c5a37fd87f03a5968e17fc6dfc8499b1517bf78ffbbae79ac3d4de45ef468174bc3cf63d8bf394bab73e783fd05892aed8e8313e94a47e561cc5819085f0b8f37f6496e4593e94640a6a22541002ca8166e045704da98b46ffc3e5e0d7", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "7905" + }, + { + "content-type": "text/css; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 21:38:51 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "flUDwRXAcKGlCZV+B6xp1kix2zMM2jCaLr8GXWfOS9o=" + }, + { + "x-cnection": "close" + }, + { + "cache-control": "public, max-age=31191685" + }, + { + "expires": "Wed, 30 Oct 2013 13:12:25 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:00 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 49, + "wire": "8854012a0f0d03393630ce6c96df3dbf4a01d532db52820044a081702edc0814c5a37fdd7f03a440a3dd66a9b76ab2a20720b9ffb9ef53fba7a6a6b2e6db0a32f7ef8374fd5df3df64e083d55892aed8e8313e94a47e561cc5804dbadb2d85ef6496e4593e9413ca436cca08016540b571972e080a62d1bf6196dc34fd280654d27eea0801128115c6c3700253168dffebe6dd", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "960" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Thu, 07 Jun 2012 20:17:10 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "s2bSrOgSOrnc1I2Y+hCmZNjO4JKRAsJvvEShk7xvQh0=" + }, + { + "x-cnection": "close" + }, + { + "cache-control": "public, max-age=25753518" + }, + { + "expires": "Wed, 28 Aug 2013 14:36:20 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:02 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 50, + "wire": "88c3d36c96c361be940b2a65b68504008940b3700ddc69f53168dfe2e77f03a593776c3a3a337760e977359d0f8d17f87adda79f91158fd85ae9d7fd9b3f8d97dbedfef4410f0d830b420f5892aed8e8313e94a47e561cc5804dbadb2db4df6496e4593e9413ca436cca08016540b571972e34ea98b46fc2efe1dc", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Fri, 13 Jul 2012 13:05:49 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "dSqFMj3BQam7KrjoHsDUySNYx2e/ZA4jk+iLwQD5q+M=" + }, + { + "content-length": "1421" + }, + { + "cache-control": "public, max-age=25753545" + }, + { + "expires": "Wed, 28 Aug 2013 14:36:47 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:02 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-cnection": "close" + } + ] + }, + { + "seqno": 51, + "wire": "88c70f0d8365f75d5f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ed424e3b1054c16a6559ef6c96dc34fd282754d444a820044a08371a0dc65e53168dffe77f03a334a1cfeb062bd833398184e3c3939f6df21369714a2e746361ebda49dd20d1ba36c907df5892aed8e8313e94a47e561cc5819085c105d67f6496e4593e94640a6a22541002ca806ee0017196d4c5a37fc7f4efe6", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "3977" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Sat, 27 Oct 2012 21:41:38 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "if1LyEGCEK6E/tHFIYqTdcReGf2YlH/8CNcvt0MSb5c=" + }, + { + "x-cnection": "close" + }, + { + "cache-control": "public, max-age=31162173" + }, + { + "expires": "Wed, 30 Oct 2013 05:00:35 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:02 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 52, + "wire": "88ccdc6c96df3dbf4a044a435d8a0801128066e019b80654c5a37febf07f02a3068d9d91ad2eee1ec9307b7e9465c1d41e0c5bc0f7ebd77966efd3d3532092eda5a83f0f0d033331365892aed8e8313e94a47e561cc5804db82782273f6496df697e9413aa436cca080165403371a72e09f53168df6196dc34fd280654d27eea0801128115c6c3700ca98b46ff408721eaa8a4498f5788ea52d6b0e83772ffece7", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Thu, 12 Apr 2012 03:03:03 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "0MQqsPt7SaQdEz9msJEk0wieC0zyyvfgvjy4gscfRm4=" + }, + { + "content-length": "316" + }, + { + "cache-control": "public, max-age=25628126" + }, + { + "expires": "Tue, 27 Aug 2013 03:46:29 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:03 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-cnection": "close" + } + ] + }, + { + "seqno": 53, + "wire": "885f88352398ac74acb37f0f0d8371d75f6c96e4593e9403ea681fa5040089410ae36ddc6c0a62d1bf52848fd24a8fc2c1588ba47e561cc5802203ee001f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6779" + }, + { + "last-modified": "Wed, 09 May 2012 22:55:50 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:03 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 54, + "wire": "88c10f0d8375e7df6c96d07abe940b4a681fa5040089403f702ddc6da53168dfc0c4c3bf", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7899" + }, + { + "last-modified": "Mon, 14 May 2012 09:15:54 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:03 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 55, + "wire": "88c20f0d8379f7036c96c361be94081486d99410022504cdc035704da98b46ffc1c06496dc34fd281754d27eea0801128115c6c3700ca98b46ffc6c5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "8961" + }, + { + "last-modified": "Fri, 10 Aug 2012 23:04:25 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=1209600" + }, + { + "expires": "Sat, 17 Nov 2012 12:51:03 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:03 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 56, + "wire": "88c40f0d83744e836c96df3dbf4a05d5340fd2820044a05db8015c138a62d1bfc3c2bfc7c6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7270" + }, + { + "last-modified": "Thu, 17 May 2012 17:02:26 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=1209600" + }, + { + "expires": "Sat, 17 Nov 2012 12:51:03 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:03 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 57, + "wire": "88c50f0d840804f35f6c96c361be940b4a6e2d6a0801128166e34cdc0054c5a37fc4c8c7c3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "10284" + }, + { + "last-modified": "Fri, 14 Sep 2012 13:43:01 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:03 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 58, + "wire": "88c60f0d8371f6456c96df697e940b6a681fa504008940b971b66e040a62d1bfc5c4c9c8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6932" + }, + { + "last-modified": "Tue, 15 May 2012 16:53:10 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=1209600" + }, + { + "date": "Sat, 03 Nov 2012 12:51:03 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 59, + "wire": "88c70f0d840bed01af6c96c361be940b2a65b6850400894106e32f5c0baa62d1bfc6c5c2cac9", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "19404" + }, + { + "last-modified": "Fri, 13 Jul 2012 21:38:17 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=1209600" + }, + { + "expires": "Sat, 17 Nov 2012 12:51:03 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:03 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 60, + "wire": "8858bbaec3771a4bf4a54759093d85fa52a3ac419272fd294da84ad617b8e83483497e94ace84ac49ca4eb003e94aec2ac49ca4eb003e94a47e561cc58015f87352398ac4c697f6495df3dbf4a05486bb141000d2800dc006e002a62d1bf6c95df3dbf4a05486bb141000d2800dc006e000a62d1bf4085aec1cd48ff86a8eb10649cbf4089f2b4b1ad495361888f1c7b2277223a32332c2272223a32342c2271223a302c2261223a33317d4089f2b4b1ac82d9dcb67f8a08170b8d2e2034bbcfff7f15a4a67f736bd5bf77fbb2fbbf9eba5f0cd5b3ef95121cdfb5d252facc0472bdf9e6f539de836196dc34fd280654d27eea0801128115c6c3700d298b46ffd20f0d023433", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0" + }, + { + "content-type": "image/gif" + }, + { + "expires": "Thu, 1 Apr 2004 01:01:01 GMT" + }, + { + "last-modified": "Thu, 1 Apr 2004 01:01:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-fb-metrics": "{\"w\":23,\"r\":24,\"q\":0,\"a\":31}" + }, + { + "x-fb-server": "10.164.204.89" + }, + { + "x-fb-debug": "mhzgPOTS+rD7XyjD1gp3zWldoiZpmeeyK0sWCXxCmL8=" + }, + { + "date": "Sat, 03 Nov 2012 12:51:04 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "43" + } + ] + }, + { + "seqno": 61, + "wire": "88c6c5c4c3c27f021d7b2277223a31332c2272223a3133372c2271223a302c2261223a32337d7f028a08170b8d2e171d5db67f7f02a3b795e739f86f2f44bc9adcc5a3930023bd9a46c37ad3fec92a30db4fab07d346ecf820c1d50f0d023433", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0" + }, + { + "content-type": "image/gif" + }, + { + "expires": "Thu, 1 Apr 2004 01:01:01 GMT" + }, + { + "last-modified": "Thu, 1 Apr 2004 01:01:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-fb-metrics": "{\"w\":13,\"r\":137,\"q\":0,\"a\":23}" + }, + { + "x-fb-server": "10.164.167.53" + }, + { + "x-fb-debug": "uWC6Yw5Jjt8tp6GMW/0c7q4sQiyN+cfsFumyrajMSLE=" + }, + { + "date": "Sat, 03 Nov 2012 12:51:04 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "43" + } + ] + }, + { + "seqno": 62, + "wire": "88c9d4c7c6c57f011c7b2277223a32302c2272223a34332c2271223a302c2261223a33307d7f018908170b8daed89771df7f01a4cce6a7fdfbc5523a75c3c09d38d5dcf366c15bdcbd73cd7efb6c0776d859cbabb6ff6f41c4d80f0d84081965af5a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Thu, 1 Apr 2004 01:01:01 GMT" + }, + { + "last-modified": "Thu, 1 Apr 2004 01:01:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-fb-metrics": "{\"w\":20,\"r\":43,\"q\":0,\"a\":30}" + }, + { + "x-fb-server": "10.165.52.67" + }, + { + "x-fb-debug": "K6O9zzGnsjkFUcjVnvogKEp8WyYKDD5/1SRA3JOqTz8=" + }, + { + "date": "Sat, 03 Nov 2012 12:51:04 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "10334" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 63, + "wire": "88cdd8cbcac97f021c7b2277223a34302c2272223a33332c2271223a302c2261223a32327d7f028908170b8d2e102eebff7f02a69b87fbcdb6f6de5670affd8f06fddc70c22ff7b3b7bcadbba2cf4dfa78cfe9fdc9bb6fbe2d41c8dc0f0d8475f005ffc1", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0" + }, + { + "content-type": "image/jpeg" + }, + { + "expires": "Thu, 1 Apr 2004 01:01:01 GMT" + }, + { + "last-modified": "Thu, 1 Apr 2004 01:01:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-fb-metrics": "{\"w\":40,\"r\":33,\"q\":0,\"a\":22}" + }, + { + "x-fb-server": "10.164.10.79" + }, + { + "x-fb-debug": "gU+KRCRWrUp+aETSVFA2+QqzJ57Mry5y8i9NZISRzV4=" + }, + { + "date": "Sat, 03 Nov 2012 12:51:04 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "79019" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 64, + "wire": "88d05f87352398ac5754dfcfcecdc1c07f00a5166e7259a64ff7de1e918f90bfdd0fd1fa023fdb655b365b9ef02de3fec77f96096ac0883fcade0f0d023637c3", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0" + }, + { + "content-type": "image/png" + }, + { + "expires": "Thu, 1 Apr 2004 01:01:01 GMT" + }, + { + "last-modified": "Thu, 1 Apr 2004 01:01:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-fb-metrics": "{\"w\":40,\"r\":33,\"q\":0,\"a\":22}" + }, + { + "x-fb-server": "10.164.10.79" + }, + { + "x-fb-debug": "2KYdrNd+vAjbaW2+l9lZ0c9qQnQQuLC0uV+aDWEfnEs=" + }, + { + "date": "Sat, 03 Nov 2012 12:51:04 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "67" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 65, + "wire": "88bf7ea5a13fbcd90f8fd283cbfd3a7e7effb6316cd17b27eb99ffdf0f79c9c708fa35ccaf1300e683cbdf0f0d023637", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "x-fb-debug": "ltZY31wZe0x9jjXZ+/GQMCIZ6L+UzLcVFaj4Ye8cEag=" + }, + { + "date": "Sat, 03 Nov 2012 12:51:04 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "67" + } + ] + }, + { + "seqno": 66, + "wire": "88c00f0d846442103f6c96d07abe940b6a6a225410022502ddc65db80794c5a37fdde1e0dc", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "32220" + }, + { + "last-modified": "Mon, 15 Oct 2012 15:37:08 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:03 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 67, + "wire": "88c10f0d85081b740cff6c96d07abe940b6a6a225410022502ddc65eb80654c5a37fdee2e1dd", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "105703" + }, + { + "last-modified": "Mon, 15 Oct 2012 15:38:03 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:03 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 68, + "wire": "88d40f0d8465c75c7b6c96df3dbf4a002a693f750400894006e36ddc684a62d1bfdfe3e2de", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "36768" + }, + { + "last-modified": "Thu, 01 Nov 2012 01:55:42 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:03 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 69, + "wire": "88c30f0d85085f6dd6bf6c96c361be94138a6a225410022500fdc683702253168dffe0e4e3df", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "119574" + }, + { + "last-modified": "Fri, 26 Oct 2012 09:41:12 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:03 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 70, + "wire": "8854012a0f0d033635395f89352398ac7958c43d5f6c96df3dbf4a044a435d8a0801128066e019b821298b46ff4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb7f07a4f581ea7c8baead04c8f6c6b424f9af7e7387f3bbbe7768d97715b29ee83d31dbaf3c08834087f2b12a291263d5842507417f5892aed8e8313e94a47e561cc5802e3207c4e03f6496dc34fd2810a9a07e941002ca8076e045700e298b46ff6196dc34fd280654d27eea0801128115c6c3700e298b46ffecd17b8b84842d695b05443c86aa6f", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "659" + }, + { + "content-type": "image/x-icon" + }, + { + "last-modified": "Thu, 12 Apr 2012 03:03:22 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "yE8mx2kOMcI8Q4MtoKCXYAXv7xSMQBGufoB0y/qkYEs=" + }, + { + "x-cnection": "close" + }, + { + "cache-control": "public, max-age=16309260" + }, + { + "expires": "Sat, 11 May 2013 07:12:06 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:06 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 71, + "wire": "88ec0f0d8371f71c6c96df3dbf4a05d5340fd2820044a05cb8272e01c53168dfebeae76196dc34fd280654d27eea0801128115c6c3700fa98b46ffef", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6966" + }, + { + "last-modified": "Thu, 17 May 2012 16:26:06 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=1209600" + }, + { + "expires": "Sat, 17 Nov 2012 12:51:03 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 72, + "wire": "88ee0f0d8369b0076c96c361be94038a65b6850400894086e32e5c69b53168dfedbff0ec", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4501" + }, + { + "last-modified": "Fri, 06 Jul 2012 11:36:45 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 73, + "wire": "88ef0f0d8465d699176c96d07abe94034a65b6a5040089403d7196ee36253168dfeec0f1ed", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "37432" + }, + { + "last-modified": "Mon, 04 Jun 2012 08:35:52 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 74, + "wire": "88f00f0d83742fbd6c96df697e940b6a681fa504008940b971a6ee05953168dfefc1f2ee", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7198" + }, + { + "last-modified": "Tue, 15 May 2012 16:45:13 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 75, + "wire": "88d30f0d840b2d36d76c96df3dbf4a3215340fd2820044a00371a7ae32f298b46ff0c2f3ef", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "13454" + }, + { + "last-modified": "Thu, 31 May 2012 01:48:38 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 76, + "wire": "88d85f8b1d75d0620d263d4c7441ea54919d29aee30c78f1e1794642c673f55c87a7409619085421621ea4d87a161d141fc2c4b0b216a4987423834d969758b3aec3771a4bf4a54759093d85fa52a3ac419272fd294da84ad617b8e83483497e94ace84ac49ca4eb003e94aec2ac49ca4eb003e7798624f6d5d4b27f6196dc34fd280654d27eea0801128115c6c3700f298b46ff408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "content-encoding": "gzip" + }, + { + "content-type": "application/json" + }, + { + "access-control-allow-origin": "http://www.facebook.com" + }, + { + "access-control-allow-credentials": "true" + }, + { + "cache-control": "private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0" + }, + { + "pragma": "no-cache" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 12:51:08 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 77, + "wire": "88db0f0d840bedb2ef6c96df697e940bea65b6a5040089403d71b0dc13aa62d1bf52848fd24a8f588ba47e561cc5802203ee001ff6ccc1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "19537" + }, + { + "last-modified": "Tue, 19 Jun 2012 08:51:27 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=1209600" + }, + { + "expires": "Sat, 17 Nov 2012 12:51:03 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 78, + "wire": "885f88352398ac74acb37f0f0d836de6856c96d07abe940b4a681fa5040089410ae019b8d3aa62d1bfc1cec3c0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5842" + }, + { + "last-modified": "Mon, 14 May 2012 22:03:47 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 79, + "wire": "88bf0f0d8371f7596c96df697e941094d03f4a0801128266e09cb8d32a62d1bfc2cfc4c1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6973" + }, + { + "last-modified": "Tue, 22 May 2012 23:26:43 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 80, + "wire": "88c00f0d83744e336c96d07abe940854cb6d4a080112820dc086e042a62d1bffc3d0c5c2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7263" + }, + { + "last-modified": "Mon, 11 Jun 2012 21:11:11 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 81, + "wire": "88c10f0d837042176c96df697e940b6a681fa5040089400ae041702053168dffc4c36496dc34fd281754d27eea0801128115c6c3700fa98b46ffd2c7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6222" + }, + { + "last-modified": "Tue, 15 May 2012 02:10:10 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=1209600" + }, + { + "expires": "Sat, 17 Nov 2012 12:51:09 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 82, + "wire": "88c30f0d8378006f6c96c361be940b2a65b685040089400ae360b82754c5a37fc6d3c8c5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "8005" + }, + { + "last-modified": "Fri, 13 Jul 2012 02:50:27 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 83, + "wire": "88c40f0d8375f69f6c96e4593e94642a6a225410022502edc1377190298b46ffc7d4c9c6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7949" + }, + { + "last-modified": "Wed, 31 Oct 2012 17:25:30 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 84, + "wire": "88c50f0d836de6c16c96e4593e94132a681fa504008940b3700d5c036a62d1bfc8d5cac7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5850" + }, + { + "last-modified": "Wed, 23 May 2012 13:04:05 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 85, + "wire": "88c60f0d8369e7016c96c361be940bca681fa5040089403971b7ee05953168dfc9d6cbc8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4860" + }, + { + "last-modified": "Fri, 18 May 2012 06:59:13 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 86, + "wire": "88e80f0d8465c702df6c96c361be9403ca65b6a5040089403f702f5c65b53168dfcad7ccc9", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "36615" + }, + { + "last-modified": "Fri, 08 Jun 2012 09:18:35 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 87, + "wire": "88e90f0d841381781f6c96c361be940054cb6d4a0801128115c6deb82794c5a37fcbd8cdca", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "26180" + }, + { + "last-modified": "Fri, 01 Jun 2012 12:58:28 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 88, + "wire": "88c90f0d8379b6c36c96e4593e940b8a681fa50400894002e05fb820298b46ffcc6196dc34fd280654d27eea0801128115c6c3702053168dffcfcc", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "8551" + }, + { + "last-modified": "Wed, 16 May 2012 00:19:20 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 89, + "wire": "88cb0f0d8374020f6c96c361be940bca681fa5040089403d71b7ae01953168dfcebfd0cd", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7021" + }, + { + "last-modified": "Fri, 18 May 2012 08:58:03 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 90, + "wire": "88cc0f0d837822736c96df3dbf4a05d5340fd2820044a0417190dc0014c5a37fcfc0d1ce", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "8126" + }, + { + "last-modified": "Thu, 17 May 2012 10:31:00 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 91, + "wire": "88ee0f0d84105d781f6c96c361be941014cb6d0a0801128205c699b81694c5a37fd0c1d2cf", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "21780" + }, + { + "last-modified": "Fri, 20 Jul 2012 20:43:14 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 92, + "wire": "88ef0f0d840b8103ff6c96c361be9403ca65b6a50400894106e34ddc0014c5a37fd1ded3d0", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "16109" + }, + { + "last-modified": "Fri, 08 Jun 2012 21:45:00 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 93, + "wire": "88cf0f0d836c4d3d6c96df697e940b6a681fa504008940bb702fdc1094c5a37fd2dfd4d1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5248" + }, + { + "last-modified": "Tue, 15 May 2012 17:19:22 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 94, + "wire": "88d00f0d8375c0036c96df697e940b6a681fa50400894002e005702ca98b46ffd3d2e0d5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7600" + }, + { + "last-modified": "Tue, 15 May 2012 00:02:13 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=1209600" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 95, + "wire": "88d10f0d83782d3d6c96df697e94034a6e2d6a080112806ee09bb81754c5a37fd4d36496dc34fd281754d27eea0801128115c6c3700ca98b46ffe2d7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "8148" + }, + { + "last-modified": "Tue, 04 Sep 2012 05:25:17 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=1209600" + }, + { + "expires": "Sat, 17 Nov 2012 12:51:03 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 96, + "wire": "88f40f0d84132e3cf76c96df697e94036a65b6a504008940b971b7ae36e298b46fd6d56496dc34fd281754d27eea0801128115c6c3702053168dffc8d9", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "23688" + }, + { + "last-modified": "Tue, 05 Jun 2012 16:58:56 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=1209600" + }, + { + "expires": "Sat, 17 Nov 2012 12:51:10 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:10 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 97, + "wire": "88f60f0d84644dbaff6c96c361be940b6a65b6a504008941337041b8c854c5a37fd8e5dad7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "32579" + }, + { + "last-modified": "Fri, 15 Jun 2012 23:21:31 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 98, + "wire": "88f70f0d8465c0b6d76c96df697e940b6a681fa504008940b971b66e01a53168dfd9e6dbd8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "36154" + }, + { + "last-modified": "Tue, 15 May 2012 16:53:04 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 99, + "wire": "88d70f0d83784e076c96df3dbf4a05d5340fd2820044a05cb816ee05f53168dfdae7dcd9", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "8261" + }, + { + "last-modified": "Thu, 17 May 2012 16:15:19 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 100, + "wire": "885f87352398ac5754df0f0d8465a700cf6c96df697e94036a65b6a504008940bf71966e32053168dfdcdbc5e9de", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "34603" + }, + { + "last-modified": "Tue, 05 Jun 2012 19:33:30 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "cache-control": "max-age=1209600" + }, + { + "expires": "Sat, 17 Nov 2012 12:51:03 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 101, + "wire": "88bf0f0d840bec881f6c96c361be941014cb6d0a0801128215c659b810298b46ffddeadfdc", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "19320" + }, + { + "last-modified": "Fri, 20 Jul 2012 22:33:10 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "date": "Sat, 03 Nov 2012 12:51:09 GMT" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "max-age=1209600" + } + ] + }, + { + "seqno": 102, + "wire": "88f55f91497ca582211f6a1271d882a60b532acf7f6c96dc34fd282754d444a820044a08371b7ee36e298b46fff45a839bd9ab7f35a3260b1abd5f6f5ae8b53fb8343659b43b839078b184fa738d0f576f69f267d5eaa726830f0d830b6f3f5892aed8e8313e94a47e561cc5819085f0b4107f6496e4593e94640a6a22541002ca8166e01eb806d4c5a37f6196dc34fd280654d27eea0801128115c6c3702da98b46ffe6f3f7", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "text/css; charset=utf-8" + }, + { + "last-modified": "Sat, 27 Oct 2012 21:59:56 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "cEr4CpqyPlutZEM5egM7EW1V/FoNLas8puqhILOyn6g=" + }, + { + "content-length": "1589" + }, + { + "cache-control": "public, max-age=31191410" + }, + { + "expires": "Wed, 30 Oct 2013 13:08:05 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:15 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-cnection": "close" + } + ] + }, + { + "seqno": 103, + "wire": "8854012ac86c96df3dbf4a044a435d8a0801128066e019b8cb4a62d1bf4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cbc57f05a3ddd23e5ef9649c8ddb7cbc26cdefa8e768fc47e304baa54769aa75f4e5ef9cdaafc3070f0d82089a5892aed8e8313e94a47e561cc5804db8c89d7dbf6496df697e9413aa436cca0801654037700d5c640a62d1bfc4ec7b8b84842d695b05443c86aa6f4087f2b12a291263d5842507417f", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Thu, 12 Apr 2012 03:03:34 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "SjbWzWIhc5uDeUgKzkah4oVawEfOfsqgn79tJvLiODA=" + }, + { + "content-length": "124" + }, + { + "cache-control": "public, max-age=25632795" + }, + { + "expires": "Tue, 27 Aug 2013 05:04:30 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:15 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-cnection": "close" + } + ] + }, + { + "seqno": 104, + "wire": "88c5cf6c96df3dbf4a044a435d8a0801128066e00571b7d4c5a37fc47f04a47aef3e70dfc86cfcbf75eded65334fab7e5aaab5b8dd6f255d33e5e76d2813ff9b2f35070f0d033137385892aed8e8313e94a47e561cc5804db8cbcf32f76496df697e9413aa436cca080165403971a6ee05953168dfcaf2", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Thu, 12 Apr 2012 03:02:59 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "8BYYADIiLWZPRqrmghOTJnnu5b75InjLJYums29XQC4=" + }, + { + "content-length": "178" + }, + { + "cache-control": "public, max-age=25638838" + }, + { + "expires": "Tue, 27 Aug 2013 06:45:13 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:15 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 105, + "wire": "88c95f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ed424e3b1054c16a6559ef6c96c361be94138a6a2254100225041b8d0ae01d53168dffc9d07f03a3b26619f210175fae4dffbe7dc2d1fa8cba26d95c992f993c2bdb2176518e4705e3841f0f0d8365a033c65892aed8e8313e94a47e561cc5819081f7dc71df6496df697e9413ea6a22541002ca810dc65fb801298b46ffcff7", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Fri, 26 Oct 2012 21:42:07 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "rg/3x10ePyW5+Yv14okaeMgQpdIDitUpRdeQlHd62wU=" + }, + { + "content-length": "3403" + }, + { + "vary": "Accept-Encoding" + }, + { + "cache-control": "public, max-age=31099667" + }, + { + "expires": "Tue, 29 Oct 2013 11:39:02 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:15 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 106, + "wire": "88cec26c96dd6d5f4a09e535112a080112820dc086e32d298b46ffcdd47f02a4ff788123bcc2c550e675c9dba03ea6fd24d87d76eb8e4e7cfa7bb5f8f4bdbf7cfbf9020f0f0d8365d7da5892aed8e8313e94a47e561cc5819081f03cf39f6496df697e9413ea6a22541002ca807ee04571a1298b46ff6196dc34fd280654d27eea0801128115c6c3702e298b46ff408721eaa8a4498f5788ea52d6b0e83772ffcecd", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 21:11:34 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "+G0d7Y1/nAK76h5l1ygZcgFyqkHdYYjzu9bN8TThTW0=" + }, + { + "content-length": "3794" + }, + { + "cache-control": "public, max-age=31090886" + }, + { + "expires": "Tue, 29 Oct 2013 09:12:42 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:16 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-cnection": "close" + } + ] + }, + { + "seqno": 107, + "wire": "88d4c86c96df3dbf4a09d53716b5040089410ae05fb82794c5a37fd37f04a5fdf2fffb04f92f4c7b23a7a340ff7bf8ecede5872ff2ea0cb84dd3c368d47b75666bcbd07f0f0d836990b95892aed8e8313e94a47e561cc5819085f0b4d0bf6496e4593e94640a6a22541002ca8166e01eb8cbca62d1bfc3c2ddd2", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Thu, 27 Sep 2012 22:19:28 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "Zx9+0hICgorbmj40+TVQqx/6DWk0JFijw5sOouOK4x8=" + }, + { + "content-length": "4316" + }, + { + "cache-control": "public, max-age=31191442" + }, + { + "expires": "Wed, 30 Oct 2013 13:08:38 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:16 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 108, + "wire": "88d8e2d0d6dd7f01a3f4fcd5819ed9e9c5e24b3a5934c8c3ebce6e15496edb71b9fbd67c7e99ca20bbe2a8600f0d023832d35892aed8e8313e94a47e561cc5804db8278220ff6496df697e9413aa436cca080165403371a72e32ea98b46fc6c5", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Thu, 12 Apr 2012 03:02:59 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "y9gp03qLmGwdrjrggsFyxKUnduRuH6ZkhHy3J217wnA=" + }, + { + "content-length": "82" + }, + { + "vary": "Accept-Encoding" + }, + { + "cache-control": "public, max-age=25628121" + }, + { + "expires": "Tue, 27 Aug 2013 03:46:37 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:16 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 109, + "wire": "88dbe56c96df3dbf4a044a435d8a0801128066e019b816d4c5a37fdae17f02a4f4c8727bd49fcf6e6cc1dafc6d739deab3bf81dcbed9d92589c3bf8ef972d787238ea20f0f0d8213c15892aed8e8313e94a47e561cc5804db82782277f6496df697e9413aa436cca080165403371a72e34ca98b46fcac9d9d8", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Thu, 12 Apr 2012 03:03:15 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "y31IzOtXz6QEqDb4Yh8nL9E7Jz3QdrtFTVTfJpFI67s=" + }, + { + "content-length": "281" + }, + { + "cache-control": "public, max-age=25628127" + }, + { + "expires": "Tue, 27 Aug 2013 03:46:43 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:16 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-cnection": "close" + } + ] + }, + { + "seqno": 110, + "wire": "88dfd36c96dd6d5f4a09e535112a080112820dc03b71a794c5a37fdee57f02a324998eed52b9ba3c3360e08b5eadefdaf9f5f5205ac9d77239f8b5eaf057d326be4f410f0d840bcdbc1fdb5892aed8e8313e94a47e561cc5819081c79f701f6496df697e9413ea6a22541002ca8066e01db816d4c5a37fe4cd", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 21:07:48 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "cdKo7nf6SbFgEUsu8p8ZpYkyd14IkSsYwu8pEpjIPW8=" + }, + { + "content-length": "18581" + }, + { + "vary": "Accept-Encoding" + }, + { + "cache-control": "public, max-age=31068960" + }, + { + "expires": "Tue, 29 Oct 2013 03:07:15 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:15 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 111, + "wire": "88e3ea6c96dd6d5f4a09e535112a080112820dc13d704253168dffe2e97f02a3d043f404bea986966ef2dbdf39f03f6f19accbd7f3abdca892a7e7e19a8f4e6bc0e0200f0d033638365892aed8e8313e94a47e561cc5819081c759135f6496df697e9413ea6a22541002ca8015c681700153168dff6196dc34fd280654d27eea0801128115c6c3702ea98b46ffd2e2e1", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "text/css; charset=utf-8" + }, + { + "last-modified": "Sun, 28 Oct 2012 21:28:22 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "Mcoj0fymAm3BWRvLoE9uVgrJkXk8Wldn9hUKly6PE60=" + }, + { + "content-length": "686" + }, + { + "cache-control": "public, max-age=31067324" + }, + { + "expires": "Tue, 29 Oct 2013 02:40:01 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:17 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-cnection": "close" + } + ] + }, + { + "seqno": 112, + "wire": "88e80f0d84740f09aff26c96dd6d5f4a09e535112a080112816ae32ddc0814c5a37fe77f03a3e0e5eab2ec2bb461d0db1e3bf3bbb4983d77379389e6bb21993ce1c8d77c9959dd29e0e35892aed8e8313e94a47e561cc58190842ebecb7f6496df697e9413ea6a22541002ca8172e34cdc640a62d1bfedd6f1e6", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "70824" + }, + { + "content-type": "image/png" + }, + { + "last-modified": "Sun, 28 Oct 2012 14:35:10 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "U6CnJQe7lFM5/wvYBRcEyvixo284qs3dxFI4vIJ3Sfo=" + }, + { + "x-cnection": "close" + }, + { + "cache-control": "public, max-age=31117935" + }, + { + "expires": "Tue, 29 Oct 2013 16:43:30 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:15 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 113, + "wire": "88ec0f0d8371f6d95f951d75d0620d263d4c7959139c9d7c0fb9569681a27f6c96d07abe9413ea6a225410022502f5c6dcb8d814c5a37fec7f03a47b6eafccd958ccde43e9fdc4ced2653e97b39cf3e6f47bfc846de572ae6fcfb7269c907fe85890aed8e8313e94a47e561cc5804e0196456496df697e94038a693f7504008940b37020b811298b46ff6196dc34fd280654d27eea0801128115c6c3704053168dffdc5a839bd9abed", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "6953" + }, + { + "content-type": "application/x-shockwave-flash" + }, + { + "last-modified": "Mon, 29 Oct 2012 18:56:50 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "8ROXKJ/K5IoNZG3RcJoN8LoohKyoDW2iTe6nY9hRINI=" + }, + { + "x-cnection": "close" + }, + { + "cache-control": "public, max-age=260332" + }, + { + "expires": "Tue, 06 Nov 2012 13:10:12 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:20 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 114, + "wire": "88f30f0d8371f6d9c4c3f1c2ec5890aed8e8313e94a47e561cc5804e019643c16196dc34fd280654d27eea0801128115c6c3704153168dffdfc0ef", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "6953" + }, + { + "content-type": "application/x-shockwave-flash" + }, + { + "last-modified": "Mon, 29 Oct 2012 18:56:50 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "8ROXKJ/K5IoNZG3RcJoN8LoohKyoDW2iTe6nY9hRINI=" + }, + { + "x-cnection": "close" + }, + { + "cache-control": "public, max-age=260331" + }, + { + "expires": "Tue, 06 Nov 2012 13:10:12 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:21 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 115, + "wire": "88f5e96c96df697e94640a6a225410022502edc69bb827d4c5a37ff4c17f06a420d16774d1db432f5fc5370cdfed179c6bc73c3cbb7e127bda05e2ad1d26d0f71bcfbd070f0d840b6e3cdf5892aed8e8313e94a47e561cc5819089f7c2107f6496df3dbf4a321535112a08016540bf700cdc0814c5a37fc5e3f3f2", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Tue, 30 Oct 2012 17:45:29 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "x-fb-debug": "casrvtlqM38DGgUK+sC64wYFWqXchCM2wnMjgM8VC98=" + }, + { + "content-length": "15685" + }, + { + "cache-control": "public, max-age=31299110" + }, + { + "expires": "Thu, 31 Oct 2013 19:03:10 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:20 GMT" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-cnection": "close" + } + ] + }, + { + "seqno": 116, + "wire": "8854012a0f0d830b6f87ee6c96df3dbf4a080a6e2d6a080112800dc08ae32da98b46ff4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb7f04a6c0ffe76cfd890eb48f472cdfefbd1e77eff53e9d7a0be38767eadd7b30fcffba5fcda3c7583f4087f2b12a291263d5842507417f5892aed8e8313e94a47e561cc5804f3aeb8265bf6496e4593e940094d444a820059502d5c0b7704d298b46ff6196dc34fd280654d27eea0801128115c6c3704fa98b46ffebcc7b8b84842d695b05443c86aa6f", + "headers": [ + { + ":status": "200" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "1591" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "last-modified": "Thu, 20 Sep 2012 01:12:35 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-fb-debug": "E9XqLqcAPtaMWK+vlxTTyhNPMewUq9nSCKax+m9KMwk=" + }, + { + "x-cnection": "close" + }, + { + "cache-control": "public, max-age=28776235" + }, + { + "expires": "Wed, 02 Oct 2013 14:15:24 GMT" + }, + { + "date": "Sat, 03 Nov 2012 12:51:29 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_27.json b/http/http-client/src/test/resources/hpack-test-case/story_27.json new file mode 100644 index 000000000..8c4215005 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_27.json @@ -0,0 +1,10328 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "488264026196dc34fd280654d27eea0801128166e341b811298b46ff4003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcf0f28c6a0e41d06f63498f5405a96b50ab376d42acddb51f6a17cd66b0a88370d3f4a002b693f758400b4a059b8d06e044a62d1bfed4ac699e063ed490f48cd540bcb4189d6c5c87a7f0f1f919d29aee30c78f1e17968313ad8b90f4b1f5885aec3771a4b4089f2b20b6772c8b47ebf94f1e3c05f7d7968313ad8bd36c8bfa1ce73ae43d37b8b84842d695b05443c86aa6f5a839bd9ab0f0d023230408721eaa8a4498f57842507417f5f92497ca589d34d1f6a1271d882a60e1bf0acf7", + "headers": [ + { + ":status": "302" + }, + { + "date": "Sat, 03 Nov 2012 13:41:12 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "set-cookie": "localization=en-us%3Bus%3Bus; expires=Sat, 01-Nov-2014 13:41:12 GMT; path=/; domain=.flickr.com" + }, + { + "location": "http://www.flickr.com/" + }, + { + "cache-control": "private" + }, + { + "x-served-by": "www199.flickr.mud.yahoo.com" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "20" + }, + { + "connection": "close" + }, + { + "content-type": "text/html; charset=UTF-8" + } + ] + }, + { + "seqno": 1, + "wire": "886196dc34fd280654d27eea0801128166e341b81654c5a37fc56c96dc34fd280654d27eea0801128005c037700153168dff52848fd24a8fc4c3c5408bf2b4b4189d6c59091a4c4f013158a1a8eb2127b0bf4a547588324e5fa529b5095ac2f71d0690692fd2948fcac398b0034085aec1cd48ff86a8eb10649cbf5f92497ca589d34d1f6a1271d882a60b532acf7f5501307cecc7bf7eb602b854b02e2fe895997a6d917f439ce75ea2a54feb98e739f7d83965313716cee5b180ae202e2029ffb26846e954ffe7f7f4a63dfbf5b015c2a58012fe895997a4c35fd0e739d7a8a953fae639ce7df60e594c4dc5b3b96c602b880b880a7fec9a11ba553ff9fdff7688e7bf73015c405c40798624f6d5d4b27f7f0b88ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:13 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Sat, 03 Nov 2012 00:05:01 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www199.flickr.mud.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "no-store, no-cache, must-revalidate, max-age=0" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "age": "0" + }, + { + "via": "HTTP/1.1 r16.ycpi.mud.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ]), HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 2, + "wire": "886196dc34fd280654d27eea0801128166e341b816d4c5a37fd15895aec3771a4bf4a54759093d85fa5291f9587316007fcfcdc15f911d75d0620d263d4c795ba0fb8d04b0d5a7cf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:15 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "private, no-store, max-age=0" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 3, + "wire": "886196e4593e94642a6a225410022500fdc6c1704253168dffd46c96c361be94038a65b68504008940bf702ddc69c53168dfccd2d17f1494f1e3c09b5e5a0c4eb62f4db22fe8739ceb90f4ffcc588ca47e561cc58190b6cb800001cb5f87352398ac5754df558513ac81b67f7cebc7bf7eb602b854b1a12fe895997a6d917f439ce75ea2a54feb98e739f7d83965313716cee5b180ae202e2029ffb2634292a9ffcfefe94c7bf7eb602b854b0025fd12b32f4986bfa1ce73af5152a7f5cc739cfbec1cb2989b8b6772d8c05710171014ffd931a14954ffe7f7cac9c80f0d8465b0805f6496d07abe9413ca65b6850400b4a099b8c82e000a62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Wed, 31 Oct 2012 09:50:22 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Fri, 06 Jul 2012 19:15:46 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www25.flickr.mud.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "max-age=315360000" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/png" + }, + { + "age": "273053" + }, + { + "via": "HTTP/1.1 r42.ycpi.mud.yahoo.net (YahooTrafficServer/1.20.20 [cHs f ]), HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cHs f ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "35102" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + } + ] + }, + { + "seqno": 4, + "wire": "886196df3dbf4a002a693f750400894035704cdc65e53168dfdc6c96df697e94038a681d8a0801128266e34f5c03ca62d1bfd40f0d84105e641f7f0694f1e3c05a6d7968313ad8bd36c8bfa1ce73ae43d3c1c5c45585101c136eff7cebc7bf7eb602b854b1a717f44accbd36c8bfa1ce73af5152a7f5cc739cfbec1cb2989b8b6772d8c05710171014ffd931a14954ffe7f7f4a63dfbf5b015c2a58fafe895997a4c35fd0e739d7a8a953fae639ce7df60e594c4dc5b3b96c602b880b880a7fec98d0a4aa7ff3fbfd0ce", + "headers": [ + { + ":status": "200" + }, + { + "date": "Thu, 01 Nov 2012 04:23:38 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Tue, 06 Mar 2012 23:48:08 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "21830" + }, + { + "x-served-by": "www145.flickr.mud.yahoo.com" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + }, + { + "cache-control": "max-age=315360000" + }, + { + "content-type": "image/png" + }, + { + "age": "206257" + }, + { + "via": "HTTP/1.1 r46.ycpi.mud.yahoo.net (YahooTrafficServer/1.20.20 [cHs f ]), HTTP/1.1 r9.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cHs f ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 5, + "wire": "88cde0c9d7dddc7f0194f1e3c05e757968313ad8bd36c8bfa1ce73ae43d3d7e0d5dbd37cecc7bf7eb602b854b1a697f44accbd36c8bfa1ce73af5152a7f5cc739cfbec1cb2989b8b6772d8c05710171014ffd9342374aa7ff3fbfa531efdfad80ae152c0097f44accbd261afe8739cebd454a9fd731ce73efb072ca626e2d9dcb63015c405c4053ff64d08dd2a9ffcfeffd2d1d00f0d8465b0805fc50f28c6a0e41d06f63498f5405a96b50ab376d42acddb51f6a17cd66b0a88370d3f4a002b693f758400b4a059b8d06e05b53168dff6a5634cf031f6a487a466aa05e5a0c4eb62e43d3f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:15 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Fri, 06 Jul 2012 19:15:46 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www187.flickr.mud.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=UTF-8" + }, + { + "age": "0" + }, + { + "via": "HTTP/1.1 r44.ycpi.mud.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ]), HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "35102" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + }, + { + "set-cookie": "localization=en-us%3Bus%3Bus; expires=Sat, 01-Nov-2014 13:41:15 GMT; path=/; domain=.flickr.com" + } + ] + }, + { + "seqno": 6, + "wire": "886196dc34fd280654d27eea0801128166e341b81714c5a37fe30f28c332505420c7a8d20400005b702cbef38ebf00f8761fba3d6818ffe4a82a0200002db8165f79c75f83ed4ac699e063ed490f48cd540b8ea1d1e9262217f439ce75c87a7f400274738a028cb6da9210208aa287d86496dc34fd280654d27eea0801128166e341b81754c5a37f5899a8eb10649cbf4a5761bb8d25fa529b5095ac2f71d0690692ff0f0d03333237dd408b4d8327535532c848d36a3f8b96b2288324aa26c193a964e4e2d2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:16 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "set-cookie": "itsessionid10001561398679=aUqazlyMaa|fses10001561398679=; path=/; domain=.analytics.yahoo.com" + }, + { + "ts": "0 355 dc10_ne1" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:17 GMT" + }, + { + "cache-control": "no-cache, private, must-revalidate" + }, + { + "content-length": "327" + }, + { + "accept-ranges": "bytes" + }, + { + "tracking-status": "fpc site tracked" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "content-type": "application/x-javascript" + } + ] + }, + { + "seqno": 7, + "wire": "886196dc34fd280654d27eea0801128166e09eb82654c5a37f7f29ff27acf4189eac2cb07f33a535dc618ad9ad7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70298b571fe76c96dc34fd280654d27eea0801128166e001702fa98b46ffe17b9384842d695b05443c86aa6fae082d8b43316a4fe75889a47e561cc58197000f5f92497ca58ae819aafb50938ec415305a99567b55033737330f0d8408422701dc7687877ee6195c4b83", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:28:23 GMT" + }, + { + "p3p": "policyref=\"http://p3p.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE GOV\"" + }, + { + "last-modified": "Sat, 03 Nov 2012 13:00:19 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding,User-Agent" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "max-age=3600" + }, + { + "content-type": "text/plain; charset=utf-8" + }, + { + "age": "773" + }, + { + "content-length": "111260" + }, + { + "connection": "keep-alive" + }, + { + "server": "ATS/3.2.0" + } + ] + }, + { + "seqno": 8, + "wire": "88caef5894a8eb10649cbf4a54759093d85fa52bb0ddc692ffe40f0d023433eb5f87352398ac4c697f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:16 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 9, + "wire": "886196dc34fd280654d27eea0801128166e341b817d4c5a37ff2dbe9efee7f1094f1e3c05e5e5a0c4eb62f4db22fe8739ceb90f4ffe9f2e7e6e57cecc7bf7eb602b854b1a797f44accbd36c8bfa1ce73af5152a7f5cc739cfbec1cb2989b8b6772d8c05710171014ffd9342374aa7ff3fbfa531efdfad80ae152c0097f44accbd261afe8739cebd454a9fd731ce73efb072ca626e2d9dcb63015c405c4053ff64d08dd2a9ffcfeffe4e3e20f0d8465b0805fd70f28c096890a9291259281d53405a96b51f6a17cd66b0a8839164fa50025b28ea58400b2a059b8d06e05f53168dff6a5634cf031f6a487a466aa05e5a0c4eb62e43d3f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:19 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Fri, 06 Jul 2012 19:15:46 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www18.flickr.mud.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "age": "0" + }, + { + "via": "HTTP/1.1 r48.ycpi.mud.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ]), HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "35102" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + }, + { + "set-cookie": "fldetectedlang=en-us; expires=Wed, 02-Jan-2013 13:41:19 GMT; path=/; domain=.flickr.com" + } + ] + }, + { + "seqno": 10, + "wire": "886196dc34fd280654d27eea0801128166e341b820298b46ff5f88352398ac74acb37f0f0d836c0017e47f0cff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcf5892a47e561cc58190b6cb800001f55db1d0627f6496d07abe94640a681fa50401094082e320b80794e1bef76c96d07abe94640a436cca0801028072e01db8cb2a62d1bf52848fd24a8f558469b79a174085f2b10649cb9fc7937a92d87a54ae73a4e419272b6102f2d06275b17191a5fd0e739d721e9f408af2b10649cab5073f5b6ba1c7937a92d87a54ae73a4e419272b6102f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad840bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:20 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5002" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 10:30:08 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:33 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "45842" + }, + { + "x-cache": "HIT from photocache510.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache510.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache510.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 11, + "wire": "88c8c70f0d8313cd3fedc6c56496d07abe94640a681fa50401094082e361b8cbea70df7b6c96d07abe94640a436cca0801028072e01db8c814c5a37fc4558479e038cf7f049fc7937a92d87a54ae73a4e419272b6d017968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b6d017968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cadb405e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:20 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2849" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 10:51:39 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:30 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "88063" + }, + { + "x-cache": "HIT from photocache540.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache540.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache540.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 12, + "wire": "88cecd0f0d8365b659408721eaa8a4498f5788ea52d6b0e83772ffcdcc6496df3dbf4a09d535112a0802128215c6437190a9c37def6c96d07abe94640a436cca0801028072e01db82754c5a37fcb5585700fb4d3ff7f059fc7937a92d87a54ae73a4e419272b6c817968313ad8b8c8d2fe8739ceb90f4f7f05a1c7937a92d87a54ae73a4e419272b6c817968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cadb205e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:20 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3533" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Thu, 27 Oct 2022 22:31:31 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:27 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "609449" + }, + { + "x-cache": "HIT from photocache530.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache530.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache530.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 13, + "wire": "88d5d40f0d83782e37c4d3d26496dd6d5f4a019532db42820084a01db8cb570215386fbd6c96df697e940814d27eea08007d4102e05bb8cb4a62d1bfd155830b2c8b7f049fc7937a92d87a54ae73a4e419272b22697968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b22697968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cac89a5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:20 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "8165" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sun, 03 Jul 2022 07:34:11 UTC" + }, + { + "last-modified": "Tue, 10 Nov 2009 20:15:34 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "1332" + }, + { + "x-cache": "HIT from photocache324.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache324.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache324.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 14, + "wire": "88dbda0f0d840b80681fcad9d8d06c96e4593e940b6a6e2d6a080102817ee34cdc644a62d1bfd65585081a75c7bf7f039fc7937a92d87a54ae73a4e419272b61797968313ad8b8c8d2fe8739ceb90f4f7f03a1c7937a92d87a54ae73a4e419272b61797968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad85e5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:20 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "16040" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 10:51:39 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:43:32 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "104768" + }, + { + "x-cache": "HIT from photocache518.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache518.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache518.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 15, + "wire": "88e0df0f0d840b8f05efcfdeddd56c96e4593e940b6a6e2d6a080102817ee34cdc0bea62d1bfdbc2cccbca", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:20 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "16818" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 10:51:39 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:43:19 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "104768" + }, + { + "x-cache": "HIT from photocache530.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache530.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache530.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 16, + "wire": "88e1e00f0d85136f3ccbdfd0dfde6496d07abe94640a681fa5040109403f702d5c1014e1bef76c96d07abe94640a436cca0801028072e01db8cb4a62d1bfdd558365f6417f059fc7937a92d87a54ae73a4e419272b6cb4bcb4189d6c5c64697f439ce75c87a77f05a2c7937a92d87a54ae73a4e419272b6cb4bcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2d2f2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:20 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "258838" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 09:14:20 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:34 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "3930" + }, + { + "x-cache": "HIT from photocache534.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache534.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache534.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 17, + "wire": "88e7e60f0d840b6d859fd6e5e46496d07abe94640a681fa5040109403f71a76e32d29c37de6c96d07abe94038a65b6a50400854086e08571b7d4c5a37fe355837dc69b7f049fc7937a92d87a54ae73a4e419272b6012f2d06275b17191a5fd0e739d721e9f7f04a1c7937a92d87a54ae73a4e419272b6012f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad804bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:20 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "15513" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 09:47:34 UTC" + }, + { + "last-modified": "Mon, 06 Jun 2011 11:22:59 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "9645" + }, + { + "x-cache": "HIT from photocache502.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache502.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache502.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 18, + "wire": "88edeb0f28c332505420c7a8d20400005b702cbef38ebf00f8761fba3d6818ffe4a82a0200002db8165f79c75f83ed4ac699e063ed490f48cd540b8ea1d1e9262217f439ce75c87a7f400274738a028cbc0524204315450f4085aec1cd48ff86a8eb10649cbf6496dc34fd280654d27eea0801128166e341b820a98b46ff5899a8eb10649cbf4a5761bb8d25fa529b5095ac2f71d0690692ff0f0d023436eb408b4d8327535532c848d36a3f8b96b2288324aa26c193a9647b8b84842d695b05443c86aa6f7f23842507417f5f911d75d0620d263d4c795ba0fb8d04b0d5a7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:20 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "set-cookie": "itsessionid10001561398679=aUqazlyMaa|fses10001561398679=; path=/; domain=.analytics.yahoo.com" + }, + { + "ts": "0 380 dc11_ne1" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:21 GMT" + }, + { + "cache-control": "no-cache, private, must-revalidate" + }, + { + "content-length": "46" + }, + { + "accept-ranges": "bytes" + }, + { + "tracking-status": "fpc site tracked" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "content-type": "application/x-javascript" + } + ] + }, + { + "seqno": 19, + "wire": "886196dc34fd280654d27eea0801128166e341b820a98b46ff5f88352398ac74acb37f0f0d8375e75ee64003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcf5892a47e561cc58190b6cb800001f55db1d0627f6496e4593e941014cb6d0a0802128115c133702f29c37def6c96d07abe94132a65b6a504003ca05fb816ee01a53168df52848fd24a8f5584682eb4e77f119fc7937a92d87a54ae73a4e419272842d2f2d06275b17a6d917f439ce75c87a77f11a2c7937a92d87a54ae73a4e419272842d2f2d06275b17a6d917f439ce75c87a6e3ccff7cae0ae152b9ce9390649ca10b4bcb4189d6c5e9b645fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:21 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7878" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Wed, 20 Jul 2022 12:23:18 UTC" + }, + { + "last-modified": "Mon, 23 Jun 2008 19:15:04 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "41746" + }, + { + "x-cache": "HIT from photocache114.flickr.mud.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache114.flickr.mud.yahoo.com:83" + }, + { + "via": "1.1 photocache114.flickr.mud.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 20, + "wire": "88c8c65894a8eb10649cbf4a54759093d85fa52bb0ddc692ffd00f0d023433cb5f87352398ac4c697f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:21 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 21, + "wire": "88cac90f0d8365965ff1c8c76496dc34fd2800a9a889504010940b571a7ee36d29c37def6c96d07abe940054d444a820044a019b8d82e32253168dffc6558475f7402f7f069fc7937a92d87a54ae73a4e419272be012f2d06275b178e50afe8739ceb90f4f7f06a1c7937a92d87a54ae73a4e419272be012f2d06275b178e50afe8739ceb90f4dc7997cae0ae152b9ce9390649caf804bcb4189d6c5e3942bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:21 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3339" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sat, 01 Oct 2022 14:49:54 UTC" + }, + { + "last-modified": "Mon, 01 Oct 2012 03:50:32 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "79702" + }, + { + "x-cache": "HIT from photocache902.flickr.bf1.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache902.flickr.bf1.yahoo.com:83" + }, + { + "via": "1.1 photocache902.flickr.bf1.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 22, + "wire": "88d0ce6c96c361be94038a65b68504008940bf702ddc69c53168dfcbd45a839bd9ab4089f2b20b6772c8b47ebf94f1e3c05a6d7968313ad8bd36c8bfa1ce73ae43d3408bf2b4b4189d6c59091a4c4f01315885aec3771a4bdc5f96497ca58e83ee3412c3569fb50938ec415305a99567bf5501307cebc7bf7eb602b854b0025fd12b32f4db22fe8739cebd454a9fd731ce73efb072ca626e2d9dcb63015c405c4053ff64d08dd2a9ffcfefe94c7bf7eb602b854b0025fd12b32f4986bfa1ce73af5152a7f5cc739cfbec1cb2989b8b6772d8c05710171014ffd9342374aa7ff3fb7688e7bf73015c405c40798624f6d5d4b27f7f1d88ea52d6b0e83772ff0f0d8465b0805f6496d07abe9413ca65b6850400b4a099b8c82e000a62d1bf0f28c096890a9291259281d53405a96b51f6a17cd66b0a8839164fa50025b28ea58400b2a059b8d06e05f53168dff6a5634cf031f6a487a466aa05e5a0c4eb62e43d3f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:21 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Fri, 06 Jul 2012 19:15:46 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www145.flickr.mud.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/javascript; charset=utf-8" + }, + { + "age": "0" + }, + { + "via": "HTTP/1.1 r02.ycpi.mud.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ]), HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "35102" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + }, + { + "set-cookie": "fldetectedlang=en-us; expires=Wed, 02-Jan-2013 13:41:19 GMT; path=/; domain=.flickr.com" + } + ] + }, + { + "seqno": 23, + "wire": "886196dc34fd280654d27eea0801128166e341b821298b46ffdc0f0d8365a033c0dbda6496d07abe94640a681fa50401094086e00370025386fbdf6c96df3dbf4a05c53716b504008140bb702fdc69d53168dfd955850b4e3826bf7f119fc7937a92d87a54ae73a4e419272b6cbcbcb4189d6c5c64697f439ce75c87a77f11a2c7937a92d87a54ae73a4e419272b6cbcbcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2f2f2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:22 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3403" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 11:01:02 UTC" + }, + { + "last-modified": "Thu, 16 Sep 2010 17:19:47 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "146624" + }, + { + "x-cache": "HIT from photocache538.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache538.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache538.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 24, + "wire": "88c4e20f0d8364017bc6e1e06496d07abe94640a681fa50401094086e32ddc038a70df7b6c96df3dbf4a05c53716b504008140bb702fdc642a62d1bfdf558569f79c6c1f7f049fc7937a92d87a54ae73a4e419272b607d7968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b607d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81f5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:22 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3018" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 11:35:06 UTC" + }, + { + "last-modified": "Thu, 16 Sep 2010 17:19:31 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "498650" + }, + { + "x-cache": "HIT from photocache509.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache509.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache509.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 25, + "wire": "88cae80f0d8365a7d9cce7e6c96c96df3dbf4a05c53716b504008140bb702fdc69f53168dfe455850bcd3a173f7f039fc7937a92d87a54ae73a4e419272b60757968313ad8b8c8d2fe8739ceb90f4f7f03a1c7937a92d87a54ae73a4e419272b60757968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81d5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:22 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3493" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 11:01:02 UTC" + }, + { + "last-modified": "Thu, 16 Sep 2010 17:19:49 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "184716" + }, + { + "x-cache": "HIT from photocache507.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache507.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache507.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 26, + "wire": "88cfed0f0d840be071cfd1ecebc86c96df3dbf4a05c53716b504008140bb702fdc65f53168dfe9c77f029fc7937a92d87a54ae73a4e419272b6cb4bcb4189d6c5c64697f439ce75c87a77f02a2c7937a92d87a54ae73a4e419272b6cb4bcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2d2f2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:22 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "19066" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 11:35:06 UTC" + }, + { + "last-modified": "Thu, 16 Sep 2010 17:19:39 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "498650" + }, + { + "x-cache": "HIT from photocache534.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache534.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache534.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 27, + "wire": "88d3f10f0d840b4dbcefd5f0efcc6c96e4593e940b6a6e2d6a080102817ee34d5c032a62d1bfedcb7f029fc7937a92d87a54ae73a4e419272b6112f2d06275b17191a5fd0e739d721e9f7f02a1c7937a92d87a54ae73a4e419272b6112f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad844bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:22 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "14587" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 11:35:06 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:44:03 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "498650" + }, + { + "x-cache": "HIT from photocache512.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache512.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache512.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 28, + "wire": "88d75f88352398ac74acb37f0f0d840b8db41fda4003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcf5892a47e561cc58190b6cb800001f55db1d0627f6496d07abe94640a681fa5040109403f71b0dc65f5386fbd6c96e4593e940b6a6e2d6a080102817ee34cdc6db53168df52848fd24a8f558471a0b4dfdad9d8", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:22 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "16541" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 09:51:39 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:43:55 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "64145" + }, + { + "x-cache": "HIT from photocache538.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache538.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache538.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 29, + "wire": "88dec40f0d840bae38ffe0c3c2d76c96e4593e940b6a6e2d6a080102817ee34cdc69e53168dfc0558569f780e83f7f0a9fc7937a92d87a54ae73a4e419272b6cb8bcb4189d6c5c64697f439ce75c87a77f0aa2c7937a92d87a54ae73a4e419272b6cb8bcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2e2f2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:22 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "17669" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 11:35:06 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:43:48 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "498070" + }, + { + "x-cache": "HIT from photocache536.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache536.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache536.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 30, + "wire": "88e3c90f0d840ba003ffe5c8c7dc6c96e4593e940b6a6e2d6a080102817ee34cdc680a62d1bfc5c27f029fc7937a92d87a54ae73a4e419272b627d7968313ad8b8c8d2fe8739ceb90f4f7f02a1c7937a92d87a54ae73a4e419272b627d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89f5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:22 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "17009" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 11:35:06 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:43:40 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "498070" + }, + { + "x-cache": "HIT from photocache529.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache529.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache529.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 31, + "wire": "88e7cd0f0d84084007c1e9cccb6496d07abe94640a681fa5040109403f71b05c69f5386fbd6c96d07abe94640a436cca0801028072e01db8c854c5a37fca5585081a75c67f7f049fc7937a92d87a54ae73a4e419272b60797968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b60797968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81e5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:22 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "110090" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 09:50:49 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:31 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "104763" + }, + { + "x-cache": "HIT from photocache508.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache508.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache508.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 32, + "wire": "88edd30f0d840882169befd2d16496d07abe94640a681fa5040109403f702d5c1094e1bef76c96e4593e940b6a6e2d6a080102817ee34cdc0bea62d1bfd0c37f039fc7937a92d87a54ae73a4e419272b60717968313ad8b8c8d2fe8739ceb90f4f7f03a1c7937a92d87a54ae73a4e419272b60717968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81c5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:22 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "121145" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 09:14:22 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:43:19 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "104763" + }, + { + "x-cache": "HIT from photocache506.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache506.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache506.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 33, + "wire": "886196dc34fd280654d27eea0801128166e341b82654c5a37fd86c96c361be94038a65b68504008940bf702ddc69c53168dfd57b8b84842d695b05443c86aa6f5a839bd9ab4089f2b20b6772c8b47ebf94f1e3c040daf2d06275b17a6d917f439ce75c87a7408bf2b4b4189d6c59091a4c4f01315885aec3771a4b4085aec1cd48ff86a8eb10649cbf5f92497ca589d34d1f6a1271d882a60b532acf7f5501307cecc7bf7eb602b854b00f2fe895997a6d917f439ce75ea2a54feb98e739f7d83965313716cee5b180ae202e2029ffb26846e954ffe7f7f4a63dfbf5b015c2a58012fe895997a4c35fd0e739d7a8a953fae639ce7df60e594c4dc5b3b96c602b880b880a7fec9a11ba553ff9fdff7688e7bf73015c405c40798624f6d5d4b27f408721eaa8a4498f5788ea52d6b0e83772ff0f0d8465b0805f6496d07abe9413ca65b6850400b4a099b8c82e000a62d1bf0f28c6a0e41d06f63498f5405a96b50ab376d42acddb51f6a17cd66b0a88370d3f4a002b693f758400b4a059b8d06e09953168dff6a5634cf031f6a487a466aa05e5a0c4eb62e43d3f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:23 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Fri, 06 Jul 2012 19:15:46 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www105.flickr.mud.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "age": "0" + }, + { + "via": "HTTP/1.1 r08.ycpi.mud.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ]), HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "35102" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + }, + { + "set-cookie": "localization=en-us%3Bus%3Bus; expires=Sat, 01-Nov-2014 13:41:23 GMT; path=/; domain=.flickr.com" + } + ] + }, + { + "seqno": 34, + "wire": "886196dc34fd280654d27eea0801128166e341b82694c5a37fe70f28c332505420c7a8d20400005b702cbef38ebf00f8761fba3d6818ffe4a82a0200002db8165f79c75f83ed4ac699e063ed490f48cd540b8ea1d1e9262217f439ce75c87a7f400274738a028c89e524209c8aa287c76496dc34fd280654d27eea0801128166e341b826d4c5a37f5899a8eb10649cbf4a5761bb8d25fa529b5095ac2f71d0690692ff0f0d023436e6408b4d8327535532c848d36a3f8b96b2288324aa26c193a964cf7f05842507417f5f911d75d0620d263d4c795ba0fb8d04b0d5a7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:24 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "set-cookie": "itsessionid10001561398679=aUqazlyMaa|fses10001561398679=; path=/; domain=.analytics.yahoo.com" + }, + { + "ts": "0 328 dc26_ne1" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:25 GMT" + }, + { + "cache-control": "no-cache, private, must-revalidate" + }, + { + "content-length": "46" + }, + { + "accept-ranges": "bytes" + }, + { + "tracking-status": "fpc site tracked" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "content-type": "application/x-javascript" + } + ] + }, + { + "seqno": 35, + "wire": "88c4ee0f0d836d9745c6edec6496dd6d5f4a09a532db42820084a01bb8066e01b5386fbd6c96dc34fd28102996da1410020502f5c6deb800a98b46ffeb5585085975f77f7f1a9fc7937a92d87a54ae73a4e419272b41757968313ad8b8c8d2fe8739ceb90f4f7f1aa1c7937a92d87a54ae73a4e419272b41757968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad05d5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:24 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5372" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sun, 24 Jul 2022 05:03:05 UTC" + }, + { + "last-modified": "Sat, 10 Jul 2010 18:58:01 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "113797" + }, + { + "x-cache": "HIT from photocache417.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache417.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache417.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 36, + "wire": "88caf35894a8eb10649cbf4a54759093d85fa52bb0ddc692ffd30f0d023433c65f87352398ac4c697f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:24 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 37, + "wire": "886196dc34fd280654d27eea0801128166e341b826d4c5a37f5f88352398ac74acb37f0f0d8408020745d04003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcf5892a47e561cc58190b6cb800001f55db1d0627f6496d07abe94640a681fa5040109403f702cdc6c0a70df7b6c96e4593e940b6a6e2d6a080102817ee34cdc65953168df52848fd24a8f558565f7c226bf7f0b9fc7937a92d87a54ae73a4e419272b6012f2d06275b17191a5fd0e739d721e9f7f0ba1c7937a92d87a54ae73a4e419272b6012f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad804bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:25 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "101072" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 09:13:50 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:43:33 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "399124" + }, + { + "x-cache": "HIT from photocache502.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache502.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache502.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 38, + "wire": "88c8c6e5c2e4e37f2394f1e3c0596d7968313ad8bd36c8bfa1ce73ae43d3e2e1e0df5501337cecc7bf7eb602b854b19697f44accbd36c8bfa1ce73af5152a7f5cc739cfbec1cb2989b8b6772d8c05710171014ffd9342374aa7ff3fbfa531efdfad80ae152c0097f44accbd261afe8739cebd454a9fd731ce73efb072ca626e2d9dcb63015c405c4053ff64d08dd2a9ffcfeffdedddc0f0d8465b0805fdb0f28c6a0e41d06f63498f5405a96b50ab376d42acddb51f6a17cd66b0a88370d3f4a002b693f758400b4a059b8d06e09b53168dff6a5634cf031f6a487a466aa05e5a0c4eb62e43d3f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:25 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Fri, 06 Jul 2012 19:15:46 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www135.flickr.mud.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "age": "3" + }, + { + "via": "HTTP/1.1 r34.ycpi.mud.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ]), HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "35102" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + }, + { + "set-cookie": "localization=en-us%3Bus%3Bus; expires=Sat, 01-Nov-2014 13:41:25 GMT; path=/; domain=.flickr.com" + } + ] + }, + { + "seqno": 39, + "wire": "886196dc34fd280654d27eea0801128166e341b82714c5a37fca0f28c332505420c7a8d20400005b702cbef38ebf00f8761fba3d6818ffe4a82a0200002db8165f79c75f83ed4ac699e063ed490f48cd540b8ea1d1e9262217f439ce75c87a7f7f1b8a028cb6f292104f455143e46496dc34fd280654d27eea0801128166e341b82754c5a37fda0f0d023436c8d9ead8d7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:26 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "set-cookie": "itsessionid10001561398679=aUqazlyMaa|fses10001561398679=; path=/; domain=.analytics.yahoo.com" + }, + { + "ts": "0 358 dc28_ne1" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:27 GMT" + }, + { + "cache-control": "no-cache, private, must-revalidate" + }, + { + "content-length": "46" + }, + { + "accept-ranges": "bytes" + }, + { + "tracking-status": "fpc site tracked" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "content-type": "application/x-javascript" + } + ] + }, + { + "seqno": 40, + "wire": "88c0ccd0e50f0d023433d8cf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:26 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 41, + "wire": "88c0ccd0e50f0d023433d8cf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:26 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 42, + "wire": "886196dc34fd280654d27eea0801128166e341b82754c5a37fcdd1e60f0d023433d9d0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:27 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 43, + "wire": "88bece0f0d840b217842e0cdcc6496d07abe94640a681fa5040109403f702cdc0bea70df7b6c96e4593e940b6a6e2d6a080102817ee34cdc680a62d1bfcb558565f7c226ff7f0b9fc7937a92d87a54ae73a4e419272b627d7968313ad8b8c8d2fe8739ceb90f4f7f0ba1c7937a92d87a54ae73a4e419272b627d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89f5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:27 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "131822" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 09:13:19 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:43:40 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "399125" + }, + { + "x-cache": "HIT from photocache529.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache529.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache529.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 44, + "wire": "88c4d3f2cff1f07f0b94f1e3c05f797968313ad8bd36c8bfa1ce73ae43d3efeeedeccac9e9e8e70f0d8465b0805fe60f28c6a0e41d06f63498f5405a96b50ab376d42acddb51f6a17cd66b0a88370d3f4a002b693f758400b4a059b8d06e09d53168dff6a5634cf031f6a487a466aa05e5a0c4eb62e43d3f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:27 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Fri, 06 Jul 2012 19:15:46 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www198.flickr.mud.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "age": "3" + }, + { + "via": "HTTP/1.1 r34.ycpi.mud.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ]), HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "35102" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + }, + { + "set-cookie": "localization=en-us%3Bus%3Bus; expires=Sat, 01-Nov-2014 13:41:27 GMT; path=/; domain=.flickr.com" + } + ] + }, + { + "seqno": 45, + "wire": "886196dc34fd280654d27eea0801128166e341b82794c5a37fd50f28c332505420c7a8d20400005b702cbef38ebf00f8761fba3d6818ffe4a82a0200002db8165f79c75f83ed4ac699e063ed490f48cd540b8ea1d1e9262217f439ce75c87a7f7f098a028cb8e292119722a8a1ef6496dc34fd280654d27eea0801128166e341b827d4c5a37fe50f0d023436d3e4f5e3e2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:28 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "set-cookie": "itsessionid10001561398679=aUqazlyMaa|fses10001561398679=; path=/; domain=.analytics.yahoo.com" + }, + { + "ts": "0 366 dc36_ne1" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:29 GMT" + }, + { + "cache-control": "no-cache, private, must-revalidate" + }, + { + "content-length": "46" + }, + { + "accept-ranges": "bytes" + }, + { + "tracking-status": "fpc site tracked" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "content-type": "application/x-javascript" + } + ] + }, + { + "seqno": 46, + "wire": "886196dc34fd280654d27eea0801128166e341b827d4c5a37fd8dcf10f0d023433e4db", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:29 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 47, + "wire": "88bed90f0d850b2ebae3bfebd8d76496d07abe94640a681fa5040109403f702cdc034a70df7b6c96e4593e940b6a6e2d6a080102817ee34cdc69e53168dfd655857db7dc79ef7f099fc7937a92d87a54ae73a4e419272b6112f2d06275b17191a5fd0e739d721e9f7f09a1c7937a92d87a54ae73a4e419272b6112f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad844bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:29 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "137767" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 09:13:04 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:43:48 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "959688" + }, + { + "x-cache": "HIT from photocache512.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache512.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache512.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 48, + "wire": "886196dc34fd280654d27eea0801128166e341b8c814c5a37fdf6c96c361be94038a65b68504008940bf702ddc69c53168dfdc7b8b84842d695b05443c86aa6f5a839bd9ab7f0d93f1e3c09a5e5a0c4eb62f1ca15fd0e739d721e9408bf2b4b4189d6c59091a4c4f01315885aec3771a4b4085aec1cd48ff86a8eb10649cbf5f92497ca589d34d1f6a1271d882a60b532acf7f5501317cb5c7bf7eb602b854b0025fd12b32f4986bfa1ce73af5152a7f5cc739cfbec1cb2989b8b6772d8c05710171014ffd9342374aa7ff3fbf7688e7bf73015c405c40798624f6d5d4b27f408721eaa8a4498f5788ea52d6b0e83772ff0f0d8465b0805f6496d07abe9413ca65b6850400b4a099b8c82e000a62d1bf0f28c6a0e41d06f63498f5405a96b50ab376d42acddb51f6a17cd66b0a88370d3f4a002b693f758400b4a059b8d06e32053168dff6a5634cf031f6a487a466aa05e5a0c4eb62e43d3f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Fri, 06 Jul 2012 19:15:46 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www24.flickr.bf1.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "age": "1" + }, + { + "via": "HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "35102" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + }, + { + "set-cookie": "localization=en-us%3Bus%3Bus; expires=Sat, 01-Nov-2014 13:41:30 GMT; path=/; domain=.flickr.com" + } + ] + }, + { + "seqno": 49, + "wire": "88ccee0f0d837dd6dbbfedec6496d07abe94640a681fa504010940b3704edc682a70df7b6c96d07abe94034a6a225410020504cdc086e05e53168dffeb558575e65d71ef7f139fc7937a92d87a54ae73a4e419272b62717968313ad8b8c8d2fe8739ceb90f4f7f13a1c7937a92d87a54ae73a4e419272b62717968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89c5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "9755" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 13:27:41 UTC" + }, + { + "last-modified": "Mon, 04 Oct 2010 23:11:18 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "783768" + }, + { + "x-cache": "HIT from photocache526.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache526.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache526.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 50, + "wire": "88d2f40f0d8378017bc5f3f2c36c96d07abe94034a6a225410020504cdc086e05c53168dfff0558575e65d71df7f039fc7937a92d87a54ae73a4e419272b620af2d06275b17191a5fd0e739d721e9f7f03a1c7937a92d87a54ae73a4e419272b620af2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad882bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "8018" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 13:27:41 UTC" + }, + { + "last-modified": "Mon, 04 Oct 2010 23:11:16 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "783767" + }, + { + "x-cache": "HIT from photocache521.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache521.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache521.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 51, + "wire": "88d75f88352398ac74acb37f0f0d836db03fcb4003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcf5892a47e561cc58190b6cb800001f55db1d0627fcb6c96d07abe94034a6a225410020504cdc086e05f53168dff52848fd24a8f558579a7dd107f7f079fc7937a92d87a54ae73a4e419272b61757968313ad8b8c8d2fe8739ceb90f4f7f07a1c7937a92d87a54ae73a4e419272b61757968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad85d5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5509" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 13:27:41 UTC" + }, + { + "last-modified": "Mon, 04 Oct 2010 23:11:19 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "849721" + }, + { + "x-cache": "HIT from photocache517.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache517.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache517.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 52, + "wire": "88e0c60f0d84134269afd3c5c4d1c3c25585799109967f7f029fc7937a92d87a54ae73a4e419272b60657968313ad8b8c8d2fe8739ceb90f4f7f02a1c7937a92d87a54ae73a4e419272b60657968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad8195e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "24244" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 13:27:41 UTC" + }, + { + "last-modified": "Mon, 04 Oct 2010 23:11:19 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "832233" + }, + { + "x-cache": "HIT from photocache503.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache503.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache503.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 53, + "wire": "88e4ca0f0d840b6e3effd7c9c86496d07abe94640a681fa5040109408ae34fdc69c5386fbd6c96df3dbf4a05c53716b504008140bb702fdc6da53168dfc855850bcd3a26bf7f049fc7937a92d87a54ae73a4e419272b6cb6bcb4189d6c5c64697f439ce75c87a77f04a2c7937a92d87a54ae73a4e419272b6cb6bcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2daf2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "15699" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:49:46 UTC" + }, + { + "last-modified": "Thu, 16 Sep 2010 17:19:54 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "184724" + }, + { + "x-cache": "HIT from photocache535.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache535.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache535.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 54, + "wire": "88ead00f0d8413806c3fddcfcedb6c96df3dbf4a05c53716b504008140bf700e5c0094c5a37fcdd57f029fc7937a92d87a54ae73a4e419272b600af2d06275b17191a5fd0e739d721e9f7f02a1c7937a92d87a54ae73a4e419272b600af2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad802bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "26051" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 13:27:41 UTC" + }, + { + "last-modified": "Thu, 16 Sep 2010 19:06:02 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "783767" + }, + { + "x-cache": "HIT from photocache501.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache501.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache501.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 55, + "wire": "88eed40f0d840b22719fe1d3d26496d07abe94640a681fa5040109408ae32f5c03ca70df7b6c96df3dbf4a05c53716b504008140bb702fdc642a62d1bfd25584105c081e7f049fc7937a92d87a54ae73a4e419272b626d7968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b626d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89b5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "13263" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:38:08 UTC" + }, + { + "last-modified": "Thu, 16 Sep 2010 17:19:31 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "216108" + }, + { + "x-cache": "HIT from photocache525.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache525.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache525.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 56, + "wire": "88f4da0f0d840bec89afe7d9d86496dd6d5f4a019532db42820084a085704cdc13aa70df7b6c96df3dbf4a05c53716b504008140bb702fdc69f53168dfd85585640175917f7f049fc7937a92d87a54ae73a4e419272b6cbebcb4189d6c5c64697f439ce75c87a77f04a2c7937a92d87a54ae73a4e419272b6cbebcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2faf2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "19324" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sun, 03 Jul 2022 22:23:27 UTC" + }, + { + "last-modified": "Thu, 16 Sep 2010 17:19:49 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "301732" + }, + { + "x-cache": "HIT from photocache539.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache539.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache539.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 57, + "wire": "886196dc34fd280654d27eea0801128166e341b8c814c5a37fe10f0d84105b759feee0df6496d07abe94640a681fa504010940b3704edc684a70df7b6c96df3dbf4a05c53716b504008140bb702fdc69d53168dfdf55840b4e34d77f059fc7937a92d87a54ae73a4e419272b6212f2d06275b17191a5fd0e739d721e9f7f05a1c7937a92d87a54ae73a4e419272b6212f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad884bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "21573" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 13:27:42 UTC" + }, + { + "last-modified": "Thu, 16 Sep 2010 17:19:47 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "14644" + }, + { + "x-cache": "HIT from photocache522.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache522.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache522.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 58, + "wire": "88c4e70f0d84101d641f408721eaa8a4498f5788ea52d6b0e83772ffe7e6c46c96e4593e940b6a6e2d6a080102817ee34d5c032a62d1bfe55585640179f07f7f049fc7937a92d87a54ae73a4e419272b62797968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b62797968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89e5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "20730" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 13:27:42 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:44:03 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "301890" + }, + { + "x-cache": "HIT from photocache528.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache528.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache528.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 59, + "wire": "88caed0f0d84644f85cfc3ecebd66c96df3dbf4a05c53716b504008140bb702fdc65f53168dfea5585136175e6ff7f039fc7937a92d87a54ae73a4e419272b62717968313ad8b8c8d2fe8739ceb90f4f7f03a1c7937a92d87a54ae73a4e419272b62717968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89c5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "32916" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:38:08 UTC" + }, + { + "last-modified": "Thu, 16 Sep 2010 17:19:39 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "251785" + }, + { + "x-cache": "HIT from photocache526.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache526.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache526.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 60, + "wire": "88cf5f88352398ac74acb37f0f0d84138dbad7c94003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcf5892a47e561cc58190b6cb800001f55db1d0627fd16c96e4593e940b6a6e2d6a080102817ee34cdc6db53168df52848fd24a8f5585799109917f7f079fc7937a92d87a54ae73a4e419272b60717968313ad8b8c8d2fe8739ceb90f4f7f07a1c7937a92d87a54ae73a4e419272b60717968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81c5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "26574" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 13:27:42 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:43:55 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "832232" + }, + { + "x-cache": "HIT from photocache506.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache506.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache506.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 61, + "wire": "88d8c60f0d8413aeb6ffd1c5c46496d07abe94640a681fa5040109410ae01cb82654e1bef76c96e4593e940b6a6e2d6a080102817ee34cdc69e53168dfc45585640179e67fd1d0cf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "27759" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 22:06:23 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:43:48 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "301883" + }, + { + "x-cache": "HIT from photocache528.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache528.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache528.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 62, + "wire": "88dbc90f0d84085a03ffd4c8c76496d07abe94640a681fa5040109408ae32f5c0814e1bef76c96d07abe94640a436cca0801028072e01db8c854c5a37fc7c67f069fc7937a92d87a54ae73a4e419272b62697968313ad8b8c8d2fe8739ceb90f4f7f06a1c7937a92d87a54ae73a4e419272b62697968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89a5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "11409" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:38:10 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:31 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "832232" + }, + { + "x-cache": "HIT from photocache524.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache524.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache524.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 63, + "wire": "88e0ce0f0d84138d36cfd9cdcc6496dd6d5f4a09a532db42820084a01ab8db970425386fbd6c96d07abe94640a436cca0801028072e01db8cb2a62d1bfcc5583640dbdd4d3d2", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "26453" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sun, 24 Jul 2022 04:56:22 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:33 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "3058" + }, + { + "x-cache": "HIT from photocache526.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache526.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache526.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 64, + "wire": "88e3d10f0d84132113dfdcd0cfc86c96e4593e940b6a6e2d6a080102817ee34cdc65953168dfce5585640179d77f7f069fc7937a92d87a54ae73a4e419272b6cb4bcb4189d6c5c64697f439ce75c87a77f06a2c7937a92d87a54ae73a4e419272b6cb4bcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2d2f2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "23128" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 22:06:23 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:43:33 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "301877" + }, + { + "x-cache": "HIT from photocache534.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache534.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache534.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 65, + "wire": "88e8d60f0d8413c275bfe1d5d4cd6c96e4593e940b6a6e2d6a080102817ee34cdc680a62d1bfd35585640179e0ff7f039fc7937a92d87a54ae73a4e419272b606d7968313ad8b8c8d2fe8739ceb90f4f7f03a1c7937a92d87a54ae73a4e419272b606d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81b5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "28275" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 22:06:23 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:43:40 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "301881" + }, + { + "x-cache": "HIT from photocache505.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache505.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache505.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 66, + "wire": "88eddb0f0d84138271dfe6dad9d26c96e4593e940b6a6e2d6a080102817ee34cdc0bea62d1bfd85585640179d73f7f039fc7937a92d87a54ae73a4e419272b61697968313ad8b8c8d2fe8739ceb90f4f7f03a1c7937a92d87a54ae73a4e419272b61697968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad85a5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:30 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "26267" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 22:06:23 UTC" + }, + { + "last-modified": "Wed, 15 Sep 2010 19:43:19 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "301876" + }, + { + "x-cache": "HIT from photocache514.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache514.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache514.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 67, + "wire": "886196dc34fd280654d27eea0801128166e341b8c854c5a37fe00f28c332505420c7a8d20400005b702cbef38ebf00f8761fba3d6818ffe4a82a0200002db8165f79c75f83ed4ac699e063ed490f48cd540b8ea1d1e9262217f439ce75c87a7f400274738a028cb8fa92104cc551434085aec1cd48ff86a8eb10649cbf6496dc34fd280654d27eea0801128166e341b8c894c5a37f5899a8eb10649cbf4a5761bb8d25fa529b5095ac2f71d0690692ff0f0d023436e1408b4d8327535532c848d36a3f8b96b2288324aa26c193a9647b8b84842d695b05443c86aa6f7f33842507417f5f911d75d0620d263d4c795ba0fb8d04b0d5a7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:31 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "set-cookie": "itsessionid10001561398679=aUqazlyMaa|fses10001561398679=; path=/; domain=.analytics.yahoo.com" + }, + { + "ts": "0 369 dc23_ne1" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:32 GMT" + }, + { + "cache-control": "no-cache, private, must-revalidate" + }, + { + "content-length": "46" + }, + { + "accept-ranges": "bytes" + }, + { + "tracking-status": "fpc site tracked" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "content-type": "application/x-javascript" + } + ] + }, + { + "seqno": 68, + "wire": "88c6e85894a8eb10649cbf4a54759093d85fa52bb0ddc692ffc50f0d023433c05f87352398ac4c697f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:31 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 69, + "wire": "88c8eabfc60f0d023433c1be", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:31 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 70, + "wire": "88c8eabfc60f0d023433c1be", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:31 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 71, + "wire": "886196e4593e94642a6a225410022500fdc643702253168dffeb6c96df697e94038a681d8a0801128266e34f5c03aa62d1bfe9c45a839bd9ab4089f2b20b6772c8b47ebf94f1e3c3ee2f2d06275b17a6d917f439ce75c87a7f408bf2b4b4189d6c59091a4c4f0131588ba47e561cc5802203ee001fcc5f91352398ac77aa45e9312c3a0f2a57310f57558413ad08407cebc7bf7eb602b854b02dafe895997a6d917f439ce75ea2a54feb98e739f7d83965313716cee5b180ae202e2029ffb2634292a9ffcfefe94c7bf7eb602b854b0025fd12b32f4986bfa1ce73af5152a7f5cc739cfbec1cb2989b8b6772d8c05710171014ffd931a14954ffe7f77688e7bf73015c405c40798624f6d5d4b27f7f0d88ea52d6b0e83772ff0f0d8371c6816496d07abe9413ca65b6850400b4a099b8c82e000a62d1bf0f28c6a0e41d06f63498f5405a96b50ab376d42acddb51f6a17cd66b0a88370d3f4a002b693f758400b4a059b8d06e32053168dff6a5634cf031f6a487a466aa05e5a0c4eb62e43d3f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Wed, 31 Oct 2012 09:31:12 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Tue, 06 Mar 2012 23:48:07 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www96.flickr.mud.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "max-age=1209600" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/vnd.microsoft.icon" + }, + { + "age": "274220" + }, + { + "via": "HTTP/1.1 r15.ycpi.mud.yahoo.net (YahooTrafficServer/1.20.20 [cHs f ]), HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cHs f ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "6640" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + }, + { + "set-cookie": "localization=en-us%3Bus%3Bus; expires=Sat, 01-Nov-2014 13:41:30 GMT; path=/; domain=.flickr.com" + } + ] + }, + { + "seqno": 72, + "wire": "886196dc34fd280654d27eea0801128166e341b8cb2a62d1bf4003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcfcb52848fd24a8fd2cb7f0b93f1e3c057968313ad8bd36c8bfa1ce73ae43d3fca5885aec3771a4bd85f92497ca589d34d1f6a1271d882a60b532acf7f5501307cebc7bf7eb602b854b0225fd12b32f4db22fe8739cebd454a9fd731ce73efb072ca626e2d9dcb63015c405c4053ff64d08dd2a9ffcfefe94c7bf7eb602b854b0025fd12b32f4986bfa1ce73af5152a7f5cc739cfbec1cb2989b8b6772d8c05710171014ffd9342374aa7ff3fbc9c8c70f0d8371c681c60f28c6a0e41d06f63498f5405a96b50ab376d42acddb51f6a17cd66b0a88370d3f4a002b693f758400b4a059b8d06e32ca98b46ffb52b1a67818fb5243d2335502f2d06275b1721e9f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:33 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Tue, 06 Mar 2012 23:48:07 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www1.flickr.mud.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "age": "0" + }, + { + "via": "HTTP/1.1 r12.ycpi.mud.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ]), HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "6640" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + }, + { + "set-cookie": "localization=en-us%3Bus%3Bus; expires=Sat, 01-Nov-2014 13:41:33 GMT; path=/; domain=.flickr.com" + } + ] + }, + { + "seqno": 73, + "wire": "886196dc34fd280654d27eea0801128166e341b8cb4a62d1bf5f88352398ac74acb37f0f0d840be169afc9c65892a47e561cc58190b6cb800001f55db1d0627f6496d07abe94640a681fa5040109408ae32f5c03ea70df7b6c96d07abe94640a436cca0801028072e01db82754c5a37fc85585640179d17f7f279fc7937a92d87a54ae73a4e419272b60797968313ad8b8c8d2fe8739ceb90f4f7f27a1c7937a92d87a54ae73a4e419272b60797968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81e5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "19144" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:38:09 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:27 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "301872" + }, + { + "x-cache": "HIT from photocache508.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache508.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache508.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 74, + "wire": "88c6c50f0d841341083fd0cdc4c36c96d07abe94640a436cca0801028072e01db80694c5a37fcd558579a03417fff0efee", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "24110" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:38:09 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:04 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "840419" + }, + { + "x-cache": "HIT from photocache505.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache505.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache505.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 75, + "wire": "88c8c70f0d84644d01cfd2cfc66496d07abe94640a681fa504010940bd71a66e01d5386fbd6c96d07abe94640a436cca0801028072e01db81654c5a37fd055850b8e34d87f7f069fc7937a92d87a54ae73a4e419272b627d7968313ad8b8c8d2fe8739ceb90f4f7f06a1c7937a92d87a54ae73a4e419272b627d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89f5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "32406" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 18:43:07 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:13 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "166451" + }, + { + "x-cache": "HIT from photocache529.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache529.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache529.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 76, + "wire": "88cecd0f0d841000dbffd8d5cccb6c96d07abe94640a436cca0801028072e01db80754c5a37fd5558565e13ce3bf7f039fc7937a92d87a54ae73a4e419272b606d7968313ad8b8c8d2fe8739ceb90f4f7f03a1c7937a92d87a54ae73a4e419272b606d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81b5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "20059" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:38:09 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:07 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "382867" + }, + { + "x-cache": "HIT from photocache505.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache505.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache505.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 77, + "wire": "88d3d20f0d84132cb22fdddad16496d07abe94640a681fa5040109408ae341b8db6a70df7b6c96d07abe94640a436cca0801028072e01db81754c5a37fdb558471e65c077f049fc7937a92d87a54ae73a4e419272b626d7968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b626d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89b5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "23332" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:41:55 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:17 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "68360" + }, + { + "x-cache": "HIT from photocache525.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache525.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache525.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 78, + "wire": "88d9d80f0d840bcdb4cfe3e0d76496d07abe94640a681fa50401094086e09ab8d3ca70df7b6c96d07abe94640a436cca0801028072e01db80714c5a37fe1558569b79b659f7f049fc7937a92d87a54ae73a4e419272b6cbcbcb4189d6c5c64697f439ce75c87a77f04a2c7937a92d87a54ae73a4e419272b6cbcbcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2f2f2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "18543" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 11:24:48 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:06 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "458533" + }, + { + "x-cache": "HIT from photocache538.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache538.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache538.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 79, + "wire": "88dfde0f0d840b6cbeffe9e6dd6496dd6d5f4a320535112a080212800dc69eb81714e1bef76c96e4593e94642a681d8a0801028176e34edc0854c5a37fe75585684d3cdbdf7f049fc7937a92d87a54ae73a4e419272b22697968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b22697968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cac89a5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "15399" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sun, 30 Oct 2022 01:48:16 UTC" + }, + { + "last-modified": "Wed, 31 Mar 2010 17:47:11 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "424858" + }, + { + "x-cache": "HIT from photocache324.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache324.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache324.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 80, + "wire": "88e5e40f0d8413eebecfefece36496df697e940b8a436cca0802128215c65ab81754e1bef76c96d07abe940baa681fa5040081410ae001719694c5a37feddd7f039fc7937a92d87a54ae73a4e419272b400af2d06275b17191a5fd0e739d721e9f7f03a1c7937a92d87a54ae73a4e419272b400af2d06275b17191a5fd0e739d721e9b8f077cad0ae152b9ce9390649cad002bcb4189d6c5c64697f439ce75c87a6e3c153fa476b4d23025dd5f76f86ee7c0fff7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "29793" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Tue, 16 Aug 2022 22:34:17 UTC" + }, + { + "last-modified": "Mon, 17 May 2010 22:00:34 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "840419" + }, + { + "x-cache": "HIT from photocache401.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache401.flickr.ac4.yahoo.com:81" + }, + { + "via": "1.1 photocache401.flickr.ac4.yahoo.com:81 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 81, + "wire": "88eae90f0d840b2dba1f408721eaa8a4498f5788ea52d6b0e83772ff4003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcfea6496d07abe94640a681fa50401094086e05cb8cb8a70df7bdc52848fd24a8f558575e642f3dfe9e8e7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "13571" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 11:16:36 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:07 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "783188" + }, + { + "x-cache": "HIT from photocache508.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache508.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache508.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 82, + "wire": "88efee0f0d840bc169ffc2c1ed6496dd6d5f4a044a65b6a504010941337022b8cb4a70df7b6c96d07abe940baa681fa50400814106e36fdc0b8a62d1bfc155857db65971af7f099fc7937a92d87a54ae73a4e419272b41697968313ad8b8c8d2fe8739ceb90f4f7f09a1c7937a92d87a54ae73a4e419272b41697968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad05a5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "18149" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sun, 12 Jun 2022 23:12:34 UTC" + }, + { + "last-modified": "Mon, 17 May 2010 21:59:16 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "953364" + }, + { + "x-cache": "HIT from photocache414.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache414.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache414.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 83, + "wire": "886196dc34fd280654d27eea0801128166e341b8cb4a62d1bf5f88352398ac74acb37f0f0d840b6d019fcac95892a47e561cc58190b6cb800001f55db1d0627f6496d07abe94640a681fa5040109408ae32f5c03ea70df7b6c96d07abe94640a436cca0801028072e01db80654c5a37fca558565e784d83fedeceb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "15403" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:38:09 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:03 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "388250" + }, + { + "x-cache": "HIT from photocache529.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache529.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache529.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 84, + "wire": "88c3c20f0d840b4ebee7cecdc16496d07abe94640a681fa5040109410ae01cb82694e1bef76c96d07abe940baa681fa50400814106e36fdc6de53168dfcd558464400bc07f0a9fc7937a92d87a54ae73a4e419272b60717968313ad8b8c8d2fe8739ceb90f4f7f0aa1c7937a92d87a54ae73a4e419272b60717968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81c5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "14796" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 22:06:24 UTC" + }, + { + "last-modified": "Mon, 17 May 2010 21:59:58 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "320180" + }, + { + "x-cache": "HIT from photocache506.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache506.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache506.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 85, + "wire": "88c9c80f0d840bce841fd4d3c7c66c96d07abe94640a436cca0801028072e01db82694c5a37fd2558579a03417ff7f039fc7937a92d87a54ae73a4e419272b6c857968313ad8b8c8d2fe8739ceb90f4f7f03a1c7937a92d87a54ae73a4e419272b6c857968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cadb215e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "18710" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:38:09 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:24 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "840419" + }, + { + "x-cache": "HIT from photocache531.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache531.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache531.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 86, + "wire": "88cecd0f0d84644db6dfd9d8cc6496dd6d5f4a05f532db52820084a05bb800dc03ca70df7b6c96d07abe940baa681fa5040081410ae001702ca98b46ffd855846de75a0f7f049fc7937a92d87a54ae73a4e419272b4112f2d06275b17191a5fd0e739d721e9f7f04a1c7937a92d87a54ae73a4e419272b4112f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad044bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "32555" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sun, 19 Jun 2022 15:01:08 UTC" + }, + { + "last-modified": "Mon, 17 May 2010 22:00:13 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "58741" + }, + { + "x-cache": "HIT from photocache412.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache412.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache412.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 87, + "wire": "88d4d30f0d840b2e89cfdfded26496d07abe94640a681fa5040109408ae32f5c0814e1bef76c96d07abe940baa681fa50400814106e36fdc134a62d1bfdec9c8c7c6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "13726" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:38:10 UTC" + }, + { + "last-modified": "Mon, 17 May 2010 21:59:24 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "840419" + }, + { + "x-cache": "HIT from photocache531.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache531.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache531.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 88, + "wire": "88d6d50f0d8413ae380fe1e0d46496d07abe94640a681fa5040109410ae01cb8cb8a70df7b6c96d07abe940baa681fa50400814106e36fdc69d53168dfe0d07f059fc7937a92d87a54ae73a4e419272b62717968313ad8b8c8d2fe8739ceb90f4f7f05a1c7937a92d87a54ae73a4e419272b62717968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89c5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "27660" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 22:06:36 UTC" + }, + { + "last-modified": "Mon, 17 May 2010 21:59:47 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "320180" + }, + { + "x-cache": "HIT from photocache526.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache526.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache526.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 89, + "wire": "88dbda0f0d840bedb4dfe6e5d9d86c96d07abe940baa681fa50400814106e36fdc640a62d1bfe4cf7f029fc7937a92d87a54ae73a4e419272b61757968313ad8b8c8d2fe8739ceb90f4f7f02a1c7937a92d87a54ae73a4e419272b61757968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad85d5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "19545" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:38:09 UTC" + }, + { + "last-modified": "Mon, 17 May 2010 21:59:30 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "840419" + }, + { + "x-cache": "HIT from photocache517.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache517.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache517.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 90, + "wire": "88dfde0f0d84640d85efeae9dd6496d07abe94640a681fa504010940b9704f5c032a70df7b6c96e4593e94642a681d8a0801028176e34edc038a62d1bfe9558469f71e6f7f049fc7937a92d87a54ae73a4e419272b60757968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b60757968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81d5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "30518" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 16:28:03 UTC" + }, + { + "last-modified": "Wed, 31 Mar 2010 17:47:06 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "49685" + }, + { + "x-cache": "HIT from photocache507.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache507.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache507.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 91, + "wire": "88e5ef0f28c332505420c7a8d20400005b702cbef38ebf00f8761fba3d6818ffe4a82a0200002db8165f79c75f83ed4ac699e063ed490f48cd540b8ea1d1e9262217f439ce75c87a7f400274738a028c840a4846598aa2874085aec1cd48ff86a8eb10649cbf6496dc34fd280654d27eea0801128166e341b8cb6a62d1bf5899a8eb10649cbf4a5761bb8d25fa529b5095ac2f71d0690692ff0f0d023436f1408b4d8327535532c848d36a3f8b96b2288324aa26c193a9647b8b84842d695b05443c86aa6f408721eaa8a4498f57842507417f5f911d75d0620d263d4c795ba0fb8d04b0d5a7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:34 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "set-cookie": "itsessionid10001561398679=aUqazlyMaa|fses10001561398679=; path=/; domain=.analytics.yahoo.com" + }, + { + "ts": "0 310 dc33_ne1" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:35 GMT" + }, + { + "cache-control": "no-cache, private, must-revalidate" + }, + { + "content-length": "46" + }, + { + "accept-ranges": "bytes" + }, + { + "tracking-status": "fpc site tracked" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "content-type": "application/x-javascript" + } + ] + }, + { + "seqno": 92, + "wire": "886196dc34fd280654d27eea0801128166e341b8cb6a62d1bf4003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcf5894a8eb10649cbf4a54759093d85fa52bb0ddc692ffc70f0d023433c25f87352398ac4c697f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:35 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 93, + "wire": "886196dc34fd280654d27eea0801128166e341b8cbca62d1bfc10f28c332505420c7a8d20400005b702cbef38ebf00f8761fba3d6818ffe4a82a0200002db8165f79c75f83ed4ac699e063ed490f48cd540b8ea1d1e9262217f439ce75c87a7f7f0b8a028cb4252423228aa287c5ca6496dc34fd280654d27eea0801128166e341b8cbea62d1bfc952848fd24a8f7f0a894192551360c9d4b27f0f0d023433c3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:38 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "set-cookie": "itsessionid10001561398679=aUqazlyMaa|fses10001561398679=; path=/; domain=.analytics.yahoo.com" + }, + { + "ts": "0 342 dc32_ne1" + }, + { + "connection": "close" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:39 GMT" + }, + { + "cache-control": "no-cache, private, must-revalidate" + }, + { + "accept-ranges": "bytes" + }, + { + "tracking-status": "site tracked" + }, + { + "content-length": "43" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 94, + "wire": "886196dc34fd280654d27eea0801128166e341b8cbea62d1bfc66c96df697e94038a681d8a0801128266e34f5c03aa62d1bfc1cb5a839bd9ab4089f2b20b6772c8b47ebf93f1e3c3215e5a0c4eb62f1ca15fd0e739d721e9408bf2b4b4189d6c59091a4c4f01315885aec3771a4bd35f92497ca589d34d1f6a1271d882a60b532acf7f5501337cb5c7bf7eb602b854b0025fd12b32f4986bfa1ce73af5152a7f5cc739cfbec1cb2989b8b6772d8c05710171014ffd9342374aa7ff3fbf7688e7bf73015c405c40798624f6d5d4b27f7f1488ea52d6b0e83772ff0f0d8371c6816496d07abe9413ca65b6850400b4a099b8c82e000a62d1bf0f28c6a0e41d06f63498f5405a96b50ab376d42acddb51f6a17cd66b0a88370d3f4a002b693f758400b4a059b8d06e32fa98b46ffb52b1a67818fb5243d2335502f2d06275b1721e9f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:39 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Tue, 06 Mar 2012 23:48:07 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www31.flickr.bf1.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "age": "3" + }, + { + "via": "HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "6640" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + }, + { + "set-cookie": "localization=en-us%3Bus%3Bus; expires=Sat, 01-Nov-2014 13:41:39 GMT; path=/; domain=.flickr.com" + } + ] + }, + { + "seqno": 95, + "wire": "886196dc34fd280654d27eea0801128166e341b8d014c5a37fd35895aec3771a4bf4a54759093d85fa5291f9587316007fd8d7c2d6ca", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:40 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "private, no-store, max-age=0" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 96, + "wire": "886196dc34fd280654d27eea0801128166e341b8d054c5a37fd50f28c332505420c7a8d20400005b702cbef38ebf00f8761fba3d6818ffe4a82a0200002db8165f79c75f83ed4ac699e063ed490f48cd540b8ea1d1e9262217f439ce75c87a7f7f128a028cb62524205f8aa287de6496dc34fd280654d27eea0801128166e341b8d094c5a37fdd0f0d023436d1dcdbdad9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:41 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "set-cookie": "itsessionid10001561398679=aUqazlyMaa|fses10001561398679=; path=/; domain=.analytics.yahoo.com" + }, + { + "ts": "0 352 dc19_ne1" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:42 GMT" + }, + { + "cache-control": "no-cache, private, must-revalidate" + }, + { + "content-length": "46" + }, + { + "accept-ranges": "bytes" + }, + { + "tracking-status": "fpc site tracked" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "content-type": "application/x-javascript" + } + ] + }, + { + "seqno": 97, + "wire": "88768c86b19272ad78fe8e92b015c30f28d73535c03ffcd05ffa0b2d85f6c01005c082271c7de65a65c7a21d62080311aa4ffcfb52f9e919aa817496c190b5257a8a9fb53079acd615106f9edfa50025b40fd2c20059502cdc68371a0a98b46ffb5358d33c0c24682f7f19c7bdae0fe6f70da3521bfa06a5fc1c46a6bdd09d4d7baf9d4d5c36a97786e53869c8a6be1b54c9a77a97f0685376f854d7b70297b568534c3c54d5bef29a756452feed6a5ed5b7f96495dc34fd28e29a07e940befb6a0457000b800298b46f5892ace84ac49ca4eb003e94aec2ac49ca4eb003e3d90f0d023433c4de", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "imp=a$le#1351950101610_669834368_ap2101_int|; Domain=.teracent.net; Expires=Thu, 02-May-2013 13:41:41 GMT; Path=/tase" + }, + { + "p3p": "CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"" + }, + { + "expires": "Sat, 6 May 1995 12:00:00 GMT" + }, + { + "cache-control": "post-check=0, pre-check=0" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:41:41 GMT" + }, + { + "connection": "close" + } + ] + }, + { + "seqno": 98, + "wire": "88c4dbdae30f0d023433ded9", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:41 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 99, + "wire": "886196dc34fd280654d27eea0801128166e341b8d34a62d1bfdcd3d6e0d27f1294f1e3c36e2f2d06275b178e50afe8739ceb90f4ffd1d0e5cf550130cecdcccb0f0d8371c681ca0f28c6a0e41d06f63498f5405a96b50ab376d42acddb51f6a17cd66b0a88370d3f4a002b693f758400b4a059b8d06e34d298b46ffb52b1a67818fb5243d2335502f2d06275b1721e9f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:44 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Tue, 06 Mar 2012 23:48:07 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www56.flickr.bf1.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "age": "0" + }, + { + "via": "HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "6640" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + }, + { + "set-cookie": "localization=en-us%3Bus%3Bus; expires=Sat, 01-Nov-2014 13:41:44 GMT; path=/; domain=.flickr.com" + } + ] + }, + { + "seqno": 100, + "wire": "88c05f88352398ac74acb37f0f0d8313cc83ccdf5892a47e561cc58190b6cb800001f55db1d0627f6496d07abe94640a681fa5040109403b71b05c0b8a70df7b6c96d07abe940054d27eea080102806ee341b820a98b46ffdc558368007f7f309fc7937a92d87a54ae73a4e419272b600af2d06275b17191a5fd0e739d721e9f7f30a1c7937a92d87a54ae73a4e419272b600af2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad802bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:44 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2830" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 07:50:16 UTC" + }, + { + "last-modified": "Mon, 01 Nov 2010 05:41:21 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "4009" + }, + { + "x-cache": "HIT from photocache501.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache501.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache501.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 101, + "wire": "88c8c50f0d8369e79ad3e6c46496d07abe94640a681fa5040109403b71a6ae09b5386fbd6c96d07abe940054d27eea080102806ee341b8d854c5a37fe255850b6f85a77f7f049fc7937a92d87a54ae73a4e419272b6202f2d06275b17191a5fd0e739d721e9f7f04a1c7937a92d87a54ae73a4e419272b6202f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad880bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:44 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4884" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 07:44:25 UTC" + }, + { + "last-modified": "Mon, 01 Nov 2010 05:41:51 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "159147" + }, + { + "x-cache": "HIT from photocache520.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache520.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache520.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 102, + "wire": "88cecb0f0d8365b0b3d9ecca6496d07abe94640a681fa5040109403b71a7ee32da9c37de6c96d07abe940054d27eea080102806ee341b81794c5a37fe855836801077f049fc7937a92d87a54ae73a4e419272b60757968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b60757968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81d5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:44 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3513" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 07:49:35 UTC" + }, + { + "last-modified": "Mon, 01 Nov 2010 05:41:18 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "4010" + }, + { + "x-cache": "HIT from photocache507.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache507.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache507.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 103, + "wire": "88d4d10f0d8371e7dfdff2d06496d07abe94640a681fa5040109403b71a7ee01e5386fbd6c96d07abe940054d27eea080102806ee341b821298b46ffee55851322782fbd7f049fc7937a92d87a54ae73a4e419272b61797968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b61797968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad85e5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:44 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6899" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 07:49:08 UTC" + }, + { + "last-modified": "Mon, 01 Nov 2010 05:41:22 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "2328198" + }, + { + "x-cache": "HIT from photocache518.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache518.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache518.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 104, + "wire": "88dad70f0d8369c7dbe57f1eff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcfd76496df3dbf4a0195349fba820084a099b8d3f704d29c37de6c96d07abe940054d27eea080102806ee341b8cbea62d1bff57f04a0d19376e525b0f4a95ce749c8324e56c2d2f2d06275b17191a5fd0e739d721e9f7f04a2d19376e525b0f4a95ce749c8324e56c2d2f2d06275b17191a5fd0e739d721e9b8f337cae0ae152b9ce9390649cad85a5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:44 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4695" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Thu, 03 Nov 2022 23:49:24 UTC" + }, + { + "last-modified": "Mon, 01 Nov 2010 05:41:39 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "x-cache": "MISS from photocache514.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "MISS from photocache514.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache514.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 105, + "wire": "88e0dd0f0d83136fb9ebc3dc6496d07abe94640a681fa5040109403b71b6ee34e29c37de6c96d07abe94034a6a225410020504cdc086e34fa98b46ff52848fd24a8f55856990b6117fcac9c8", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:44 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2596" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 07:55:46 UTC" + }, + { + "last-modified": "Mon, 04 Oct 2010 23:11:49 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431512" + }, + { + "x-cache": "HIT from photocache518.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache518.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache518.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 106, + "wire": "88e4e10f0d8365b0b9efc7e06496d07abe94640a681fa504010940bb71b66e32e29c37de6c96df3dbf4a05c53716b504008140bb702fdc642a62d1bfc155856402034fffdfdedd", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:44 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3516" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 17:53:36 UTC" + }, + { + "last-modified": "Thu, 16 Sep 2010 17:19:31 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "302049" + }, + { + "x-cache": "HIT from photocache501.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache501.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache501.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 107, + "wire": "88e7e40f0d8369f10bf2cae36496d07abe94640a681fa5040109408ae32d5c13ca70df7b6c96df3dbf4a05c53716b504008140bb702fdc69d53168dfc4558465c7da6b7f0b9fc7937a92d87a54ae73a4e419272b6212f2d06275b17191a5fd0e739d721e9f7f0ba1c7937a92d87a54ae73a4e419272b6212f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad884bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:44 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4922" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:34:28 UTC" + }, + { + "last-modified": "Thu, 16 Sep 2010 17:19:47 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "36944" + }, + { + "x-cache": "HIT from photocache522.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache522.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache522.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 108, + "wire": "88edea0f0d83642dbd408721eaa8a4498f5788ea52d6b0e83772ffd1ea6496d07abe94640a681fa5040109403b71a72e05b5386fbdd0ca55851322782dbb7f049fc7937a92d87a54ae73a4e419272b6112f2d06275b17191a5fd0e739d721e9f7f04a1c7937a92d87a54ae73a4e419272b6112f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad844bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:44 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3158" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 07:46:15 UTC" + }, + { + "last-modified": "Mon, 01 Nov 2010 05:41:39 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "2328157" + }, + { + "x-cache": "HIT from photocache512.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache512.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache512.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 109, + "wire": "88f3f00f0d8369b683c3d6ef6496d07abe94640a681fa5040109408ae340b81129c37def6c96df3dbf4a05c53716b504008140bb702fdc69f53168dfd0e1e0df55856402036cff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:44 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4541" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:12 UTC" + }, + { + "last-modified": "Thu, 16 Sep 2010 17:19:49 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "x-cache": "HIT from photocache507.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache507.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache507.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + }, + { + "age": "302053" + } + ] + }, + { + "seqno": 110, + "wire": "886196dc34fd280654d27eea0801128166e341b8d34a62d1bf5f88352398ac74acb37f0f0d83682d37c8db5892a47e561cc58190b6cb800001f55db1d0627f6496d07abe94640a681fa5040109403b71b0dc1054e1bef76c96d07abe940054d27eea080102806ee340b8dbea62d1bfd655851322784c877f0a9fc7937a92d87a54ae73a4e419272b60797968313ad8b8c8d2fe8739ceb90f4f7f0aa1c7937a92d87a54ae73a4e419272b60797968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81e5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:44 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4145" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 07:51:21 UTC" + }, + { + "last-modified": "Mon, 01 Nov 2010 05:40:59 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "2328231" + }, + { + "x-cache": "HIT from photocache508.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache508.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache508.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 111, + "wire": "88c6c50f0d8365a0bfcfe2c46496d07abe94640a681fa5040109408ae01cb8cbaa70df7b6c96df697e94032a436cca0801028215c6ddb8d38a62d1bfdc558571b03ce3df7f049fc7937a92d87a54ae73a4e419272b60717968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b60717968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81c5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:44 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3419" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:06:37 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:57:46 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "650868" + }, + { + "x-cache": "HIT from photocache506.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache506.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache506.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 112, + "wire": "88cccb0f0d836c0cb7d5e8ca6496d07abe94640a681fa50401094102e341b8c854e1bef76c96d07abe940054d27eea080102806ee341b801298b46ffe2558365f7df7f049fc7937a92d87a54ae73a4e419272b6cb4bcb4189d6c5c64697f439ce75c87a77f04a2c7937a92d87a54ae73a4e419272b6cb4bcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2d2f2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:44 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5035" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 20:41:31 UTC" + }, + { + "last-modified": "Mon, 01 Nov 2010 05:41:02 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "3999" + }, + { + "x-cache": "HIT from photocache534.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache534.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache534.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 113, + "wire": "88d2ee5895aec3771a4bf4a54759093d85fa5291f9587316007f7b8b84842d695b05443c86aa6f7f1e842507417f798624f6d5d4b27f5f911d75d0620d263d4c795ba0fb8d04b0d5a75a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:44 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "private, no-store, max-age=0" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 114, + "wire": "886196dc34fd280654d27eea0801128166e341b8d36a62d1bf4003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcf0f28c332505420c7a8d20400005b702cbef38ebf00f8761fba3d6818ffe4a82a0200002db8165f79c75f83ed4ac699e063ed490f48cd540b8ea1d1e9262217f439ce75c87a7f400274738a028c81b52423f15450ff4085aec1cd48ff86a8eb10649cbf6496dc34fd280654d27eea0801128166e341b8d38a62d1bf5899a8eb10649cbf4a5761bb8d25fa529b5095ac2f71d0690692ff0f0d023435f2408b4d8327535532c848d36a3f8b96b2288324aa26c193a964c9c8c6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:45 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "set-cookie": "itsessionid10001561398679=aUqazlyMaa|fses10001561398679=; path=/; domain=.analytics.yahoo.com" + }, + { + "ts": "0 305 dc9_ne1" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:46 GMT" + }, + { + "cache-control": "no-cache, private, must-revalidate" + }, + { + "content-length": "45" + }, + { + "accept-ranges": "bytes" + }, + { + "tracking-status": "fpc site tracked" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "content-type": "application/x-javascript" + } + ] + }, + { + "seqno": 115, + "wire": "88c4de0f0d836d90b3e8c3dd6496df3dbf4a002a6e2d6a080212806ae36ddc038a70df7b6c96c361be94642a436cca080112817ae09eb8db8a62d1bff555857db7dc0b3f7f119fc7937a92d87a54ae73a4e419272be0717968313ad8bc72857f439ce75c87a77f11a2c7937a92d87a54ae73a4e419272be0717968313ad8bc72857f439ce75c87a6e3ccff7cae0ae152b9ce9390649caf81c5e5a0c4eb62f1ca15fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:45 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5313" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Thu, 01 Sep 2022 04:55:06 UTC" + }, + { + "last-modified": "Fri, 31 Aug 2012 18:28:56 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "959613" + }, + { + "x-cache": "HIT from photocache906.flickr.bf1.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache906.flickr.bf1.yahoo.com:83" + }, + { + "via": "1.1 photocache906.flickr.bf1.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 116, + "wire": "88cae40f0d8369c75beec9e36496e4593e9403aa6e2d6a080212806ee36e5c6c4a70df7b6c96df3dbf4a01c53716b504008940bd71a15c6db53168df52848fd24a8f558565979f69bf7f059fc7937a92d87a54ae73a4e419272be0757968313ad8bc72857f439ce75c87a77f05a2c7937a92d87a54ae73a4e419272be0757968313ad8bc72857f439ce75c87a6e3ccff7cae0ae152b9ce9390649caf81d5e5a0c4eb62f1ca15fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:45 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4675" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Wed, 07 Sep 2022 05:56:52 UTC" + }, + { + "last-modified": "Thu, 06 Sep 2012 18:42:55 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "338945" + }, + { + "x-cache": "HIT from photocache907.flickr.bf1.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache907.flickr.bf1.yahoo.com:83" + }, + { + "via": "1.1 photocache907.flickr.bf1.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 117, + "wire": "88d1eb0f0d83744f0bf5d0ea6496dd6d5f4a09e521b665040109403f7190dc0054e1bef76c96d07abe9413aa436cca0801128215c13d704053168dffc4558475971b6b7f049fc7937a92d87a54ae73a4e419272be2697968313ad8bc72857f439ce75c87a77f04a2c7937a92d87a54ae73a4e419272be2697968313ad8bc72857f439ce75c87a6e3cdff7cae0ae152b9ce9390649caf89a5e5a0c4eb62f1ca15fd0e739d721e9b8f36a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:45 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7282" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sun, 28 Aug 2022 09:31:01 UTC" + }, + { + "last-modified": "Mon, 27 Aug 2012 22:28:20 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "73654" + }, + { + "x-cache": "HIT from photocache924.flickr.bf1.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache924.flickr.bf1.yahoo.com:85" + }, + { + "via": "1.1 photocache924.flickr.bf1.yahoo.com:85 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 118, + "wire": "88d7f10f0d83684d0b7f1c88ea52d6b0e83772ffd7f1d16c96c361be94642a436cca080112817ae09eb8db2a62d1bfcad07f039fc7937a92d87a54ae73a4e419272be2717968313ad8bc72857f439ce75c87a77f03a2c7937a92d87a54ae73a4e419272be2717968313ad8bc72857f439ce75c87a6e3ccff7cae0ae152b9ce9390649caf89c5e5a0c4eb62f1ca15fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:45 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4242" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Thu, 01 Sep 2022 04:55:06 UTC" + }, + { + "last-modified": "Fri, 31 Aug 2012 18:28:53 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "959613" + }, + { + "x-cache": "HIT from photocache926.flickr.bf1.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache926.flickr.bf1.yahoo.com:83" + }, + { + "via": "1.1 photocache926.flickr.bf1.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 119, + "wire": "88dc5f88352398ac74acb37f0f0d8369b703c3dc5892a47e561cc58190b6cb800001f55db1d0627f6496dd6d5f4a05d532db42820084a019b817ee05c5386fbd6c96d07abe940054d27eea080102806ee341b80714c5a37fd1558368006f7f069fc7937a92d87a54ae73a4e419272880caf2d06275b178e50afe8739ceb90f4f7f06a1c7937a92d87a54ae73a4e419272880caf2d06275b178e50afe8739ceb90f4dc7997cae0ae152b9ce9390649ca2032bcb4189d6c5e3942bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:45 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4561" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sun, 17 Jul 2022 03:19:16 UTC" + }, + { + "last-modified": "Mon, 01 Nov 2010 05:41:06 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "4005" + }, + { + "x-cache": "HIT from photocache203.flickr.bf1.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache203.flickr.bf1.yahoo.com:83" + }, + { + "via": "1.1 photocache203.flickr.bf1.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 120, + "wire": "88e4c50f0d837196c1cae3c46497dd6d5f4a05d532db42820084a01ab8db77196d4e1bef7f6c96d07abe940054d27eea0801028172e05eb8d054c5a37fd755856990ba27bf7f049fc7937a92d87a54ae73a4e419272880d2f2d06275b178e50afe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272880d2f2d06275b178e50afe8739ceb90f4dc7997cae0ae152b9ce9390649ca2034bcb4189d6c5e3942bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:45 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6350" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sun, 17 Jul 2022 04:55:35 UTC" + }, + { + "last-modified": "Mon, 01 Nov 2010 16:18:41 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431728" + }, + { + "x-cache": "HIT from photocache204.flickr.bf1.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache204.flickr.bf1.yahoo.com:83" + }, + { + "via": "1.1 photocache204.flickr.bf1.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 121, + "wire": "88eacb0f0d83659683d0e9ca6496dd6d5f4a05d532db42820084a05eb807ee32ea9c37de6c96d07abe940054d27eea080102806ee341b81694c5a37fdd5585132278416f7f049fc7937a92d87a54ae73a4e419272880daf2d06275b178e50afe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272880daf2d06275b178e50afe8739ceb90f4dc79b7cae0ae152b9ce9390649ca2036bcb4189d6c5e3942bfa1ce73ae43d371e6d4fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:45 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3341" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sun, 17 Jul 2022 18:09:37 UTC" + }, + { + "last-modified": "Mon, 01 Nov 2010 05:41:14 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "2328215" + }, + { + "x-cache": "HIT from photocache205.flickr.bf1.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache205.flickr.bf1.yahoo.com:85" + }, + { + "via": "1.1 photocache205.flickr.bf1.yahoo.com:85 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 122, + "wire": "88f0d10f0d8374006fd6efd06496d07abe940bca65b6850401094006e341b81129c37def6c96d07abe940054d27eea080102806ee341b8db8a62d1bfe35583680e357f049fc7937a92d87a54ae73a4e41927288025e5a0c4eb62f1ca15fd0e739d721e9f7f04a1c7937a92d87a54ae73a4e41927288025e5a0c4eb62f1ca15fd0e739d721e9b8f377cad0ae152b9ce9390649ca20097968313ad8bc72857f439ce75c87a6e3cda9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:45 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7005" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 18 Jul 2022 01:41:12 UTC" + }, + { + "last-modified": "Mon, 01 Nov 2010 05:41:56 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "4064" + }, + { + "x-cache": "HIT from photocache202.flickr.bf1.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache202.flickr.bf1.yahoo.com:85" + }, + { + "via": "1.1 photocache202.flickr.bf1.yahoo.com:85 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 123, + "wire": "886196dc34fd280654d27eea0801128166e341b8d36a62d1bfd80f0d83680e3ddd4003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcfd86496d07abe940bca65b685040109403371a6ee01a5386fbd6c96d07abe940054d27eea080102806ee341b800a98b46ffeb558569978026ffc57f05a1c7937a92d87a54ae73a4e41927288025e5a0c4eb62f1ca15fd0e739d721e9b8f337cad0ae152b9ce9390649ca20097968313ad8bc72857f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:45 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4068" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 18 Jul 2022 03:45:04 UTC" + }, + { + "last-modified": "Mon, 01 Nov 2010 05:41:01 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "438025" + }, + { + "x-cache": "HIT from photocache202.flickr.bf1.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache202.flickr.bf1.yahoo.com:83" + }, + { + "via": "1.1 photocache202.flickr.bf1.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 124, + "wire": "88c4de0f0d8375f701e3c3dd6496d07abe940bca65b685040109403371a6ae3225386fbd6c96d07abe940054d27eea080102806ee341b820298b46fff055851322784077d0cfce", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:45 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7960" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 18 Jul 2022 03:44:32 UTC" + }, + { + "last-modified": "Mon, 01 Nov 2010 05:41:20 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "2328207" + }, + { + "x-cache": "HIT from photocache205.flickr.bf1.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache205.flickr.bf1.yahoo.com:85" + }, + { + "via": "1.1 photocache205.flickr.bf1.yahoo.com:85 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 125, + "wire": "88c7c65894a8eb10649cbf4a54759093d85fa52bb0ddc692ff4085aec1cd48ff86a8eb10649cbf0f0d0234337f29842507417f5f87352398ac4c697f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:45 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 126, + "wire": "886196dc34fd280654d27eea0801128166e341b8d38a62d1bfe60f0d836997dbebcbe56496d07abe940bca65b685040109403371a6ee01b5386fbd6c96d07abe940054d27eea080102806ee340b8dbaa62d1bf52848fd24a8f55851322784d07d3cbca", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:46 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4395" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 18 Jul 2022 03:45:05 UTC" + }, + { + "last-modified": "Mon, 01 Nov 2010 05:40:57 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "2328241" + }, + { + "x-cache": "HIT from photocache202.flickr.bf1.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache202.flickr.bf1.yahoo.com:83" + }, + { + "via": "1.1 photocache202.flickr.bf1.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 127, + "wire": "886196dc34fd280654d27eea0801128166e341b8d3ca62d1bfd06c96df697e94038a681d8a0801128266e34f5c03aa62d1bfc17b8b84842d695b05443c86aa6f5a839bd9ab4089f2b20b6772c8b47ebf93f1e3c3205e5a0c4eb62f1ca15fd0e739d721e9408bf2b4b4189d6c59091a4c4f01315885aec3771a4bcc5f92497ca589d34d1f6a1271d882a60b532acf7f5501337cb5c7bf7eb602b854b0025fd12b32f4986bfa1ce73af5152a7f5cc739cfbec1cb2989b8b6772d8c05710171014ffd9342374aa7ff3fbf7688e7bf73015c405c40798624f6d5d4b27f7f1188ea52d6b0e83772ff0f0d8371c6816496d07abe9413ca65b6850400b4a099b8c82e000a62d1bf0f28c6a0e41d06f63498f5405a96b50ab376d42acddb51f6a17cd66b0a88370d3f4a002b693f758400b4a059b8d06e34f298b46ffb52b1a67818fb5243d2335502f2d06275b1721e9f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:48 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Tue, 06 Mar 2012 23:48:07 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www30.flickr.bf1.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "age": "3" + }, + { + "via": "HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "6640" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + }, + { + "set-cookie": "localization=en-us%3Bus%3Bus; expires=Sat, 01-Nov-2014 13:41:48 GMT; path=/; domain=.flickr.com" + } + ] + }, + { + "seqno": 128, + "wire": "886196dc34fd280654d27eea0801128166e341b8d3ea62d1bf5f88352398ac74acb37f0f0d8365d039c1df5892a47e561cc58190b6cb800001f55db1d0627f6496d07abe94640a681fa5040109408ae340b82694e1bef76c96d07abe940baa681fa50400814106e36fdc6de53168dfd25585700f36277f7f289fc7937a92d87a54ae73a4e419272b6212f2d06275b17191a5fd0e739d721e9f7f21a1c7937a92d87a54ae73a4e419272b6212f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad884bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3706" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:24 UTC" + }, + { + "last-modified": "Mon, 17 May 2010 21:59:58 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "608527" + }, + { + "x-cache": "HIT from photocache522.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache522.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache522.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 129, + "wire": "88c6c50f0d8369c083c8e6c46496d07abe94640a681fa504010940b971a76e36f29c37de6c96e4593e94034a436cca0801028266e083704ea98b46ffd855856df79d799f7f049fc7937a92d87a54ae73a4e419272b616d7968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b616d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad85b5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4610" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 16:47:58 UTC" + }, + { + "last-modified": "Wed, 04 Aug 2010 23:21:27 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "598783" + }, + { + "x-cache": "HIT from photocache515.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache515.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache515.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 130, + "wire": "88cccb0f0d83136d87ceecca6496d07abe94640a681fa5040109408ae340b81129c37def6c96df697e94032a436cca0801028215c6deb8d36a62d1bfde558578010b2eff7f049fc7937a92d87a54ae73a4e419272b627d7968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b627d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89f5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2551" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:12 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:45 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "801137" + }, + { + "x-cache": "HIT from photocache529.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache529.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache529.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 131, + "wire": "88d2d10f0d8313c217d4f2d06496d07abe94640a681fa50401094086e34cdc136a70df7b6c96df697e94032a436cca0801028215c6deb8d38a62d1bfe47f039fc7937a92d87a54ae73a4e419272b60697968313ad8b8c8d2fe8739ceb90f4f7f03a1c7937a92d87a54ae73a4e419272b60697968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81a5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff55850b6165f07f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2822" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 11:43:25 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:46 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "x-cache": "HIT from photocache504.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache504.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache504.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + }, + { + "age": "151390" + } + ] + }, + { + "seqno": 132, + "wire": "88d8d70f0d8369a719da4003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcfd76496d07abe94640a681fa50401094082e36e5c65d5386fbd6c96df697e94032a436cca0801028215c6ddb8cbea62d1bfeb5585136269f73f7f069fc7937a92d87a54ae73a4e419272b6202f2d06275b17191a5fd0e739d721e9f7f06a1c7937a92d87a54ae73a4e419272b6202f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad880bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4463" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 10:56:37 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:57:39 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "252496" + }, + { + "x-cache": "HIT from photocache520.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache520.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache520.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 133, + "wire": "88dfde0f0d830b4d07e1c4dd6496d07abe94640a681fa5040109408ae340b826d4e1bef76c96df697e94032a436cca0801028215c6deb810a98b46fff1558569f70026ff7f049fc7937a92d87a54ae73a4e419272b600af2d06275b17191a5fd0e739d721e9f7f04a1c7937a92d87a54ae73a4e419272b600af2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad802bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1441" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:25 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:11 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "496025" + }, + { + "x-cache": "HIT from photocache501.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache501.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache501.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 134, + "wire": "88e5e40f0d8365d781e7cae36496dc34fd282129a88950401094002e32ddc69a5386fbdf6c96e4593e94642a681d8a0801028176e34edc0854c5a37f52848fd24a8f55857de78416bf7f059fc7937a92d87a54ae73a4e419272b2112f2d06275b17191a5fd0e739d721e9f7f05a1c7937a92d87a54ae73a4e419272b2112f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cac844bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3780" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sat, 22 Oct 2022 00:35:44 UTC" + }, + { + "last-modified": "Wed, 31 Mar 2010 17:47:11 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "988214" + }, + { + "x-cache": "HIT from photocache312.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache312.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache312.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 135, + "wire": "88eceb0f0d836c416feed1ea6496d07abe94134a6a2254100425002b816ee09f5386fbdf6c96e4593e94642a681d8a0801028176e34edc03ca62d1bfc455856990bac8bf7f049fc7937a92d87a54ae73a4e419272b22697968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b22697968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cac89a5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5215" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 24 Oct 2022 02:15:29 UTC" + }, + { + "last-modified": "Wed, 31 Mar 2010 17:47:08 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431732" + }, + { + "x-cache": "HIT from photocache324.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache324.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache324.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 136, + "wire": "886196dc34fd280654d27eea0801128166e341b8d3ea62d1bf5f88352398ac74acb37f0f0d83138107408721eaa8a4498f5788ea52d6b0e83772ffda5892a47e561cc58190b6cb800001f55db1d0627f6496d07abe94640a681fa5040109408ae340b82694e1bef76c96d07abe94640a436cca0801028072e34fdc03ca62d1bfce558579d101e07fdad9d8", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2610" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:24 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:49:08 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "872080" + }, + { + "x-cache": "HIT from photocache520.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache520.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache520.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 137, + "wire": "88c4c30f0d83684d07c2dec16496d07abe94640a681fa5040109403d7000b81129c37def6c96e4593e94642a681d8a0801028176e34edc0894c5a37fd15583132db97f0b9fc7937a92d87a54ae73a4e419272b61657968313ad8b8c8d2fe8739ceb90f4f7f0ba1c7937a92d87a54ae73a4e419272b61657968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad8595e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4241" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 08:00:12 UTC" + }, + { + "last-modified": "Wed, 31 Mar 2010 17:47:12 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "2356" + }, + { + "x-cache": "HIT from photocache513.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache513.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache513.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 138, + "wire": "88cac90f0d8369a703c8e4c76496d07abe94640a681fa50401094082e041700ea9c37def6c96d07abe94640a436cca0801028072e01db80754c5a37fd75585640179c7ff7f049fc7937a92d87a54ae73a4e419272b606d7968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b606d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81b5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4461" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 10:10:07 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:07 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "301869" + }, + { + "x-cache": "HIT from photocache505.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache505.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache505.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 139, + "wire": "88d0cf0f0d83684f8bceeacd6496d07abe94640a681fa504010940b371b0dc6df5386fbd6c96d07abe94640a436cca0801028072e01db80654c5a37fdd5585136f36d03f7f049fc7937a92d87a54ae73a4e419272b620af2d06275b17191a5fd0e739d721e9f7f04a1c7937a92d87a54ae73a4e419272b620af2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad882bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4292" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 13:51:59 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:03 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "258540" + }, + { + "x-cache": "HIT from photocache521.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache521.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache521.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 140, + "wire": "88d6d50f0d836596d9d4f0d36496d07abe94640a681fa5040109408ae32d5c13ca70df7b6c96d07abe94640a436cca0801028072e34fdc0854c5a37fe37f039fc7937a92d87a54ae73a4e419272b61797968313ad8b8c8d2fe8739ceb90f4f7f03a1c7937a92d87a54ae73a4e419272b61797968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad85e5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdffec", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3353" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:34:28 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:49:11 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "x-cache": "HIT from photocache518.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache518.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache518.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + }, + { + "age": "496025" + } + ] + }, + { + "seqno": 141, + "wire": "88dbda0f0d8368007bd94003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcfd96496d07abe94640a681fa50401094102e342b80794e1bef76c96df697e94032a436cca0801028215c6ddb810298b46ffe955856996c4177f7f059fc7937a92d87a54ae73a4e419272b6212f2d06275b17191a5fd0e739d721e9f7f05a1c7937a92d87a54ae73a4e419272b6212f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad884bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4008" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 20:42:08 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:57:10 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "435217" + }, + { + "x-cache": "HIT from photocache522.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache522.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache522.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 142, + "wire": "88e2e10f0d83680f07e0c4df6496d07abe94640a681fa5040109408ae34cdc6c4a70df7b6c96df697e94032a436cca0801028215c6deb8cbea62d1bfefc3cecdcc", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4081" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:43:52 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:39 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "435217" + }, + { + "x-cache": "HIT from photocache521.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache521.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache521.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 143, + "wire": "88e4e30f0d8365f7dce2c6e16496d07abe94640a681fa504010940b771905c0854e1bef76c96e4593e94642a681d8a0801028176e34e5c6df53168dff1558579d109e7bfc5c4c3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3996" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 15:30:11 UTC" + }, + { + "last-modified": "Wed, 31 Mar 2010 17:46:59 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "872288" + }, + { + "x-cache": "HIT from photocache522.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache522.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache522.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 144, + "wire": "88e7e60f0d83105d7be5c9e4c86c96df697e94032a436cca0801028215c6deb8d34a62d1bf52848fd24a8f55856990b627ff7f099fc7937a92d87a54ae73a4e419272b62657968313ad8b8c8d2fe8739ceb90f4f7f09a1c7937a92d87a54ae73a4e419272b62657968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad8995e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2178" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 20:42:08 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:44 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431529" + }, + { + "x-cache": "HIT from photocache523.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache523.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache523.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 145, + "wire": "88edec0f0d83644f8bebcfea6496d07abe94640a681fa50401094082e32fdc682a70df7b6c96df697e94032a436cca0801028215c6ddb820a98b46ffc455856990bac8bf7f049fc7937a92d87a54ae73a4e419272b6cb2bcb4189d6c5c64697f439ce75c87a77f04a2c7937a92d87a54ae73a4e419272b6cb2bcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2caf2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3292" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 10:39:41 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:57:21 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431732" + }, + { + "x-cache": "HIT from photocache533.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache533.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache533.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 146, + "wire": "886196dc34fd280654d27eea0801128166e341b8d3ea62d1bf5f88352398ac74acb37f0f0d8365a0b7408721eaa8a4498f5788ea52d6b0e83772ffd85892a47e561cc58190b6cb800001f55db1d0627fde6c96df697e94032a436cca0801028215c6ddb8cb4a62d1bfcd558479f71d6fd7d6d5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3415" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:34:28 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:57:34 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "89675" + }, + { + "x-cache": "HIT from photocache522.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache522.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache522.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 147, + "wire": "88c3c20f0d8365f719c1dbc06496d07abe94640a681fa504010940b7702fdc6dc5386fbd6c96df697e94032a436cca0801028215c6dfb807d4c5a37fd0c8c7c6c0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3963" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 15:19:56 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:59:09 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "x-cache": "HIT from photocache533.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache533.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache533.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + }, + { + "age": "89675" + } + ] + }, + { + "seqno": 148, + "wire": "88c5c40f0d83640cb9c3ddc2e26c96e4593e94034a436cca0801028266e083704ca98b46ffd15585134db6c87f7f0b9fc7937a92d87a54ae73a4e419272b610af2d06275b17191a5fd0e739d721e9f7f0ba1c7937a92d87a54ae73a4e419272b610af2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad842bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3036" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:34:28 UTC" + }, + { + "last-modified": "Wed, 04 Aug 2010 23:21:23 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "245531" + }, + { + "x-cache": "HIT from photocache511.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache511.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache511.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 149, + "wire": "88cac90f0d830bcd33c8e2c7e76c96df697e94032a436cca0801028215c6deb8d014c5a37fd655856990b6177f7f039fc7937a92d87a54ae73a4e419272b6cbabcb4189d6c5c64697f439ce75c87a77f03a2c7937a92d87a54ae73a4e419272b6cbabcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2eaf2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1843" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:34:28 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:40 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431517" + }, + { + "x-cache": "HIT from photocache537.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache537.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache537.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 150, + "wire": "88cfce0f0d8371f743cde7ccec6c96d07abe94640a436cca0801028072e01db81654c5a37fdb5585640179d77fc2c1c0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6971" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:34:28 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:13 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "301877" + }, + { + "x-cache": "HIT from photocache537.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache537.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache537.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 151, + "wire": "88d1d00f0d8365f71ecfe9ce6496d07abe94640a681fa5040109408ae340b82694e1bef76c96d07abe94640a436cca0801028072e34fdc0094c5a37fde55856990bac87f7f069fc7937a92d87a54ae73a4e419272b62757968313ad8b8c8d2fe8739ceb90f4f7f06a1c7937a92d87a54ae73a4e419272b62757968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89d5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3968" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:24 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:49:02 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431731" + }, + { + "x-cache": "HIT from photocache527.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache527.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache527.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 152, + "wire": "88d7ef5895aec3771a4bf4a54759093d85fa5291f9587316007f7b8b84842d695b05443c86aa6f7f18842507417f798624f6d5d4b27f5f911d75d0620d263d4c795ba0fb8d04b0d5a75a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:49 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "private, no-store, max-age=0" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 153, + "wire": "886196dc34fd280654d27eea0801128166e341b8d814c5a37f4003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcf0f28c332505420c7a8d20400005b702cbef38ebf00f8761fba3d6818ffe4a82a0200002db8165f79c75f83ed4ac699e063ed490f48cd540b8ea1d1e9262217f439ce75c87a7f400274738a028c89c524204515450f4085aec1cd48ff86a8eb10649cbf6496dc34fd280654d27eea0801128166e341b8d854c5a37f5899a8eb10649cbf4a5761bb8d25fa529b5095ac2f71d0690692ff0f0d023436ee408b4d8327535532c848d36a3f8b96b2288324aa26c193a964c9c8c6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:50 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "set-cookie": "itsessionid10001561398679=aUqazlyMaa|fses10001561398679=; path=/; domain=.analytics.yahoo.com" + }, + { + "ts": "0 326 dc12_ne1" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:51 GMT" + }, + { + "cache-control": "no-cache, private, must-revalidate" + }, + { + "content-length": "46" + }, + { + "accept-ranges": "bytes" + }, + { + "tracking-status": "fpc site tracked" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "content-type": "application/x-javascript" + } + ] + }, + { + "seqno": 154, + "wire": "88c4c35894a8eb10649cbf4a54759093d85fa52bb0ddc692ffc20f0d023433c95f87352398ac4c697f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:50 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 155, + "wire": "48826402c77689e7bf73015c405c2cff408ff2b5869a74d2590c35a73a1350e92f93b075a4f601d680bd94af1ca15fd0e739d721e97f09d2acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725f52f70da3521bfa06a5fc1c46a6bdd08d4d7baf8d4d5c36a97786e52f6ad0a64d3bd4d5bef29af86d5376f87f9f0f1fa29d29aee30c0e45fd18b44948ea1cc5b1721e962b3792d1fe1a481971b03c1f84c02f58a1a8eb10649cbf4a54759093d85fa529b5095ac2f71d0690692fd2948fcac398b0037b012a6c96dc34fd280654d27eea0801128166e341b8d814c5a37f6496dc34fd280654d27eea0801128166e341b8d814c5a37fcbcf550130d2ed", + "headers": [ + { + ":status": "302" + }, + { + "date": "Sat, 03 Nov 2012 13:41:50 GMT" + }, + { + "server": "YTS/1.20.13" + }, + { + "x-rightmedia-hostname": "raptor0740.rm.bf1.yahoo.com" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID CURa ADMa DEVa PSAa PSDa OUR BUS COM INT OTC PUR STA\"" + }, + { + "location": "http://ad.yieldmanager.com/pixel?id=365081&t=2" + }, + { + "cache-control": "no-cache, no-store, must-revalidate, max-age=0" + }, + { + "vary": "*" + }, + { + "last-modified": "Sat, 03 Nov 2012 13:41:50 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:50 GMT" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 156, + "wire": "886196dc34fd280654d27eea0801128166e341b8db2a62d1bfef0f0d8369d139eecfed6496d07abe94640a681fa504010940bb71b6ae002a70df7b6c96df697e94032a436cca0801028215c6deb8cb8a62d1bf52848fd24a8f55841042079f7f1e9fc7937a92d87a54ae73a4e419272b617d7968313ad8b8c8d2fe8739ceb90f4f7f1ea1c7937a92d87a54ae73a4e419272b617d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad85f5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4726" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 17:54:01 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:36 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "211089" + }, + { + "x-cache": "HIT from photocache519.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache519.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache519.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 157, + "wire": "88c5f60f0d8369a659f5d6f46496d07abe94034a65b6850401094002e01eb8dbaa70df7b6c96d07abe94640a436cca0801028072e34fdc0b2a62d1bfc455856990bacb9f7f049fc7937a92d87a54ae73a4e419272b6cbebcb4189d6c5c64697f439ce75c87a77f04a2c7937a92d87a54ae73a4e419272b6cbebcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2faf2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4433" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 04 Jul 2022 00:08:57 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:49:13 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431736" + }, + { + "x-cache": "HIT from photocache539.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache539.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache539.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 158, + "wire": "88cb5f88352398ac74acb37f0f0d83138ebb7f2388ea52d6b0e83772ffde5892a47e561cc58190b6cb800001f55db1d0627fec6c96d07abe94640a436cca0801028072e34fdc038a62d1bfcc55856990bacb7f7f069fc7937a92d87a54ae73a4e419272b62697968313ad8b8c8d2fe8739ceb90f4f7f06a1c7937a92d87a54ae73a4e419272b62697968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89a5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2677" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:24 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:49:06 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431735" + }, + { + "x-cache": "HIT from photocache524.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache524.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache524.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 159, + "wire": "88d3c50f0d83640f07c4e4c3f16c96df697e94032a436cca0801028215c6ddb8db4a62d1bfd155850b8d3ce3bf7f039fc7937a92d87a54ae73a4e419272b610af2d06275b17191a5fd0e739d721e9f7f03a1c7937a92d87a54ae73a4e419272b610af2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad842bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3081" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:24 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:57:54 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "164867" + }, + { + "x-cache": "HIT from photocache511.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache511.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache511.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 160, + "wire": "88d8ca0f0d8365a6ddc9e9c86496d07abe94640a681fa5040109408ae32d5c13ca70df7b6c96df697e94032a436cca0801028215c6deb8cbea62d1bfd755856996c420ff7f049fc7937a92d87a54ae73a4e419272b62717968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b62717968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89c5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3457" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:34:28 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:39 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "435221" + }, + { + "x-cache": "HIT from photocache526.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache526.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache526.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 161, + "wire": "88ded00f0d8369a659cfefce6496d07abe94640a681fa5040109408ae340b81129c37def6c96df697e94032a436cca0801028215c6deb8d094c5a37fdd5585684d3820ff7f049fc7937a92d87a54ae73a4e419272b62797968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b62797968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89e5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4433" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:12 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:42 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "424621" + }, + { + "x-cache": "HIT from photocache528.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache528.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache528.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 162, + "wire": "88e4d60f0d8371a79cd57f2bff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcfd56496d07abe94640a681fa5040109408ae340b82694e1bef76c96e4593e94642a681d8a0801028176e34e5c6dc53168dfe4dd7f049fc7937a92d87a54ae73a4e419272b6d017968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b6d017968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cadb405e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "6486" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:24 UTC" + }, + { + "last-modified": "Wed, 31 Mar 2010 17:46:56 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431736" + }, + { + "x-cache": "HIT from photocache540.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache540.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache540.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 163, + "wire": "88eadc0f0d8369e7dfdbc3dac96c96e4593e94034a436cca0801028266e08371a0a98b46ffe87f029fc7937a92d87a54ae73a4e419272b60757968313ad8b8c8d2fe8739ceb90f4f7f02a1c7937a92d87a54ae73a4e419272b60757968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81d5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff55856df79d75ef", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4899" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:12 UTC" + }, + { + "last-modified": "Wed, 04 Aug 2010 23:21:41 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "x-cache": "HIT from photocache507.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache507.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache507.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + }, + { + "age": "598778" + } + ] + }, + { + "seqno": 164, + "wire": "88efe10f0d8365f783e0c8df6496dc34fd2816d4d444a820084a099b8266e01c5386fbdf6c96e4593e94642a681d8a0801028176e34e5c6dd53168dfee55856990b8f3bf7f059fc7937a92d87a54ae73a4e419272b210af2d06275b17191a5fd0e739d721e9f7f05a1c7937a92d87a54ae73a4e419272b210af2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cac842bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3981" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sat, 15 Oct 2022 23:23:06 UTC" + }, + { + "last-modified": "Wed, 31 Mar 2010 17:46:57 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431687" + }, + { + "x-cache": "HIT from photocache311.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache311.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache311.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 165, + "wire": "886196dc34fd280654d27eea0801128166e341b8db2a62d1bfe80f0d8375965ee7cfe66496dd6d5f4a044a65b6a5040109410ae01cb8d3ea70df7b6c96d07abe940baa681fa5040081410ae001719694c5a37f52848fd24a8f5585700f36cb3f7f069fc7937a92d87a54ae73a4e419272b416d7968313ad8b8c8d2fe8739ceb90f4f7f06a1c7937a92d87a54ae73a4e419272b416d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad05b5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7338" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sun, 12 Jun 2022 22:06:49 UTC" + }, + { + "last-modified": "Mon, 17 May 2010 22:00:34 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "608533" + }, + { + "x-cache": "HIT from photocache415.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache415.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache415.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 166, + "wire": "88c5ef0f0d836dc681eed6ed6496d07abe94640a681fa5040109408ae34f5c65c5386fbd6c96e4593e94642a681d8a0801028176e34edc0054c5a37fc455857de78406ff7f049fc7937a92d87a54ae73a4e419272b606d7968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b606d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81b5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5640" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:48:36 UTC" + }, + { + "last-modified": "Wed, 31 Mar 2010 17:47:01 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "988205" + }, + { + "x-cache": "HIT from photocache505.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache505.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache505.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 167, + "wire": "88cb5f88352398ac74acb37f0f0d8369c03f408721eaa8a4498f5788ea52d6b0e83772ffde5892a47e561cc58190b6cb800001f55db1d0627fde6c96df697e94032a436cca0801028215c6ddb82694c5a37fcc558579d0bc17ff7f069fc7937a92d87a54ae73a4e419272b6c817968313ad8b8c8d2fe8739ceb90f4f7f06a1c7937a92d87a54ae73a4e419272b6c817968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cadb205e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4609" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:24 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:57:24 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "871819" + }, + { + "x-cache": "HIT from photocache530.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache530.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache530.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 168, + "wire": "88d3c50f0d8365d75cc4e4c36496d07abe94640a681fa50401094082e32fdc682a70df7b6c96df697e94032a436cca0801028215c6ddb8c814c5a37fd255856990b6cb3f7f049fc7937a92d87a54ae73a4e419272b6c857968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b6c857968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cadb215e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3776" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 10:39:41 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:57:30 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431533" + }, + { + "x-cache": "HIT from photocache531.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache531.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache531.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 169, + "wire": "88d9cb0f0d8369b6c3caeac96496d07abe94640a681fa50401094086e32ddc1054e1bef76c96df697e940baa436cca080102817ae340b801298b46ffd85585682db6f3bf7f049fc7937a92d87a54ae73a4e419272b6212f2d06275b17191a5fd0e739d721e9f7f04a1c7937a92d87a54ae73a4e419272b6212f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad884bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4551" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 11:35:21 UTC" + }, + { + "last-modified": "Tue, 17 Aug 2010 18:40:02 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "415587" + }, + { + "x-cache": "HIT from photocache522.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache522.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache522.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 170, + "wire": "88dfd10f0d8369a6c5d0f0cf6496d07abe94640a681fa50401094102e341b8c854e1bef76c96df697e94032a436cca0801028215c6ddb8d34a62d1bfde558465c7db17d7d6d5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4452" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 20:41:31 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:57:44 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "36952" + }, + { + "x-cache": "HIT from photocache505.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache505.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache505.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 171, + "wire": "88e2d40f0d831004cfd34003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcfd36496d07abe94640a681fa50401094102e34ddc6db5386fbd6c96df697e94032a436cca0801028215c6ddb8d36a62d1bfe27f079fc7937a92d87a54ae73a4e419272b6cb2bcb4189d6c5c64697f439ce75c87a77f07a2c7937a92d87a54ae73a4e419272b6cb2bcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2caf2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff558571e134f3bf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2023" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 20:45:55 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:57:45 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "x-cache": "HIT from photocache533.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache533.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache533.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + }, + { + "age": "682487" + } + ] + }, + { + "seqno": 172, + "wire": "886196dc34fd280654d27eea0801128166e341b8d854c5a37fc56c96df697e94038a681d8a0801128266e34f5c03aa62d1bfe87b8b84842d695b05443c86aa6f5a839bd9ab4089f2b20b6772c8b47ebf94f1e3c05a697968313ad8bd36c8bfa1ce73ae43d3408bf2b4b4189d6c59091a4c4f01315885aec3771a4b4085aec1cd48ff86a8eb10649cbf5f92497ca589d34d1f6a1271d882a60b532acf7f5501337cecc7bf7eb602b854b04fafe895997a6d917f439ce75ea2a54feb98e739f7d83965313716cee5b180ae202e2029ffb26846e954ffe7f7f4a63dfbf5b015c2a58012fe895997a4c35fd0e739d7a8a953fae639ce7df60e594c4dc5b3b96c602b880b880a7fec9a11ba553ff9fdff7688e7bf73015c405c40798624f6d5d4b27fe70f0d8371c6816496d07abe9413ca65b6850400b4a099b8c82e000a62d1bf0f28c6a0e41d06f63498f5405a96b50ab376d42acddb51f6a17cd66b0a88370d3f4a002b693f758400b4a059b8d06e36153168dff6a5634cf031f6a487a466aa05e5a0c4eb62e43d3f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:51 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Tue, 06 Mar 2012 23:48:07 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www144.flickr.mud.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "age": "3" + }, + { + "via": "HTTP/1.1 r29.ycpi.mud.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ]), HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "6640" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + }, + { + "set-cookie": "localization=en-us%3Bus%3Bus; expires=Sat, 01-Nov-2014 13:41:51 GMT; path=/; domain=.flickr.com" + } + ] + }, + { + "seqno": 173, + "wire": "886196dc34fd280654d27eea0801128166e341b8db2a62d1bfea0f0d8369c75be9d3e86496d07abe94640a681fa50401094082e34d5c0bea70df7b6c96df697e94032a436cca0801028215c6dfb81714c5a37f52848fd24a8f558479f71d7f7f159fc7937a92d87a54ae73a4e419272b6012f2d06275b17191a5fd0e739d721e9f7f15a1c7937a92d87a54ae73a4e419272b6012f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad804bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4675" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 10:44:19 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:59:16 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "89679" + }, + { + "x-cache": "HIT from photocache502.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache502.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache502.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 174, + "wire": "88c5f10f0d83642cbff0daef6496d07abe94640a681fa50401094082e01fb8db8a70df7b6c96df697e94032a436cca0801028215c6dfb800298b46ffc4558479f71d07e9e8e7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3139" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 10:09:56 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:59:00 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "89670" + }, + { + "x-cache": "HIT from photocache531.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache531.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache531.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 175, + "wire": "88c8f40f0d83642e3bf3ddf26496d07abe94640a681fa5040109408ae340b81129c37def6c96df697e94032a436cca0801028215c6deb82714c5a37fc7ec7f069fc7937a92d87a54ae73a4e419272b616d7968313ad8b8c8d2fe8739ceb90f4f7f06a1c7937a92d87a54ae73a4e419272b616d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad85b5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3167" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:12 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:26 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431533" + }, + { + "x-cache": "HIT from photocache515.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache515.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache515.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 176, + "wire": "88cd5f88352398ac74acb37f0f0d83684277408721eaa8a4498f5788ea52d6b0e83772ffe45892a47e561cc58190b6cb800001f55db1d0627f6496d07abe94640a681fa5040109408ae340b826d4e1bef76c96df697e94032a436cca0801028215c6ddb8d38a62d1bfcfce7f069fc7937a92d87a54ae73a4e419272b606d7968313ad8b8c8d2fe8739ceb90f4f7f06a1c7937a92d87a54ae73a4e419272b606d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81b5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4227" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:25 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:57:46 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "89679" + }, + { + "x-cache": "HIT from photocache505.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache505.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache505.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 177, + "wire": "88d5c50f0d8313ae3dc4eac36496d07abe94640a681fa50401094086e34cdc0094e1bef76c96df697e94032a436cca0801028215c6ddb807d4c5a37fd47f039fc7937a92d87a54ae73a4e419272b60717968313ad8b8c8d2fe8739ceb90f4f7f03a1c7937a92d87a54ae73a4e419272b60717968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81c5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff5585101c69e7ff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2768" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 11:43:02 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:57:09 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "x-cache": "HIT from photocache506.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache506.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache506.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + }, + { + "age": "206489" + } + ] + }, + { + "seqno": 178, + "wire": "88dbcb0f0d8369975bcaf0c96496d07abe94640a681fa5040109408ae340b82694e1bef76c96d07abe940baa681fa50400814106e36fdc640a62d1bfda55857de784cb7f7f059fc7937a92d87a54ae73a4e419272b626d7968313ad8b8c8d2fe8739ceb90f4f7f05a1c7937a92d87a54ae73a4e419272b626d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89b5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4375" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:24 UTC" + }, + { + "last-modified": "Mon, 17 May 2010 21:59:30 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "988235" + }, + { + "x-cache": "HIT from photocache525.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache525.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache525.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 179, + "wire": "88e1d10f0d8365b039d04003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcfd06496d07abe94640a681fa504010940bd71b15c69d5386fbd6c96df697e94032a436cca0801028215c6ddb8cb8a62d1bfe155856990b6cb5f7f059fc7937a92d87a54ae73a4e419272b60757968313ad8b8c8d2fe8739ceb90f4f7f05a1c7937a92d87a54ae73a4e419272b60757968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81d5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3506" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 18:52:47 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:57:36 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431534" + }, + { + "x-cache": "HIT from photocache507.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache507.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache507.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 180, + "wire": "88e8d80f0d8365b10bd7c4d6ca6c96d07abe940baa681fa50400814106e36fdc134a62d1bfe655857de784cb3fdddcdb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3522" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:24 UTC" + }, + { + "last-modified": "Mon, 17 May 2010 21:59:24 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "988233" + }, + { + "x-cache": "HIT from photocache515.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache515.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache515.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 181, + "wire": "88eac65895aec3771a4bf4a54759093d85fa5291f9587316007f7b8b84842d695b05443c86aa6f7f1c842507417fef5f911d75d0620d263d4c795ba0fb8d04b0d5a75a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:53 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "private, no-store, max-age=0" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 182, + "wire": "886196dc34fd280654d27eea0801128166e341b8db4a62d1bfcc0f28c332505420c7a8d20400005b702cbef38ebf00f8761fba3d6818ffe4a82a0200002db8165f79c75f83ed4ac699e063ed490f48cd540b8ea1d1e9262217f439ce75c87a7f4002747389028c81d5242062a8a14085aec1cd48ff86a8eb10649cbf6496dc34fd280654d27eea0801128166e341b8db6a62d1bf5899a8eb10649cbf4a5761bb8d25fa529b5095ac2f71d0690692ff0f0d023435f1408b4d8327535532c848d36a3f8b96b2288324aa26c193a964c7c6c5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:54 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "set-cookie": "itsessionid10001561398679=aUqazlyMaa|fses10001561398679=; path=/; domain=.analytics.yahoo.com" + }, + { + "ts": "0 307 dc1_ne1" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:55 GMT" + }, + { + "cache-control": "no-cache, private, must-revalidate" + }, + { + "content-length": "45" + }, + { + "accept-ranges": "bytes" + }, + { + "tracking-status": "fpc site tracked" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "content-type": "application/x-javascript" + } + ] + }, + { + "seqno": 183, + "wire": "88c37689e7bf73015c405c2cff408ff2b5869a74d2590c35a73a1350e92f93b075a4f6004f857b295e3942bfa1ce73ae43d37f14d2acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725f52f70da3521bfa06a5fc1c46a6bdd08d4d7baf8d4d5c36a97786e52f6ad0a64d3bd4d5bef29af86d5376f87f9f58a1a8eb10649cbf4a54759093d85fa529b5095ac2f71d0690692fd2948fcac398b0037b012a6c96dc34fd280654d27eea0801128166e341b8db4a62d1bf6496dc34fd280654d27eea0801128166e341b8db4a62d1bfc8cb550130798624f6d5d4b27fed", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:54 GMT" + }, + { + "server": "YTS/1.20.13" + }, + { + "x-rightmedia-hostname": "raptor0291.rm.bf1.yahoo.com" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID CURa ADMa DEVa PSAa PSDa OUR BUS COM INT OTC PUR STA\"" + }, + { + "cache-control": "no-cache, no-store, must-revalidate, max-age=0" + }, + { + "vary": "*" + }, + { + "last-modified": "Sat, 03 Nov 2012 13:41:54 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:54 GMT" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 184, + "wire": "88ccda5894a8eb10649cbf4a54759093d85fa52bb0ddc692ffcb0f0d023433d05f87352398ac4c697f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:54 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 185, + "wire": "88cedcbfcc0f0d023433d1be", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:54 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 186, + "wire": "48826402cfc97f0993b075a4f601f1057b295e3942bfa1ce73ae43d3c8c7c6c5c4ced1c3c2f10f1fff0d9d29aee30c0e45fd18b44948ea1cc5b1721e960d4d7fe7ec01f21f8440eb8f3a16be37c0cfc4481d09804cbad32db420bbf176008be29805f16c13a535aacc2a8b0aa2c3e3c785e5a0c4eb62e43d2a8b0d739d2742a2c350d0321e9a4f521516148e642a2c350d263d43a065b0f50ed498881d5222b190a39293546426c1a4c7a95161ac7315954587e2c801", + "headers": [ + { + ":status": "302" + }, + { + "date": "Sat, 03 Nov 2012 13:41:54 GMT" + }, + { + "server": "YTS/1.20.13" + }, + { + "x-rightmedia-hostname": "raptor0921.rm.bf1.yahoo.com" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID CURa ADMa DEVa PSAa PSDa OUR BUS COM INT OTC PUR STA\"" + }, + { + "cache-control": "no-cache, no-store, must-revalidate, max-age=0" + }, + { + "vary": "*" + }, + { + "last-modified": "Sat, 03 Nov 2012 13:41:54 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:54 GMT" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "location": "http://ad.yieldmanager.com/imp?Z=1x1&s=768714&T=3&_salt=2374354217&B=12&m=2&u=http%3A%2F%2Fwww.flickr.com%2Fphotos%2Fnasacommons%2Ftags%2Fnationalaeronauticsandspaceadministration%2Fpage3%2F&r=0" + } + ] + }, + { + "seqno": 187, + "wire": "886196dc34fd280654d27eea0801128166e341b8db6a62d1bfdf6c96df697e94038a681d8a0801128266e34f5c03aa62d1bf52848fd24a8fd7d44089f2b20b6772c8b47ebf94f1e3c0595e5a0c4eb62f4db22fe8739ceb90f4ff408bf2b4b4189d6c59091a4c4f01315885aec3771a4bd45f92497ca589d34d1f6a1271d882a60b532acf7fca7cecc7bf7eb602b854b02f2fe895997a6d917f439ce75ea2a54feb98e739f7d83965313716cee5b180ae202e2029ffb26846e954ffe7f7f4a63dfbf5b015c2a58012fe895997a4c35fd0e739d7a8a953fae639ce7df60e594c4dc5b3b96c602b880b880a7fec9a11ba553ff9fdff7688e7bf73015c405c40cb7f1d88ea52d6b0e83772ff0f0d8371c6816496d07abe9413ca65b6850400b4a099b8c82e000a62d1bf0f28c6a0e41d06f63498f5405a96b50ab376d42acddb51f6a17cd66b0a88370d3f4a002b693f758400b4a059b8d06e36da98b46ffb52b1a67818fb5243d2335502f2d06275b1721e9f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:55 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "last-modified": "Tue, 06 Mar 2012 23:48:07 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "x-served-by": "www13.flickr.mud.yahoo.com" + }, + { + "x-flickr-static": "1" + }, + { + "cache-control": "private" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "age": "0" + }, + { + "via": "HTTP/1.1 r18.ycpi.mud.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ]), HTTP/1.1 r02.ycpi.mia.yahoo.net (YahooTrafficServer/1.20.20 [cMsSf ])" + }, + { + "server": "YTS/1.20.20" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-length": "6640" + }, + { + "expires": "Mon, 28 Jul 2014 23:30:00 GMT" + }, + { + "set-cookie": "localization=en-us%3Bus%3Bus; expires=Sat, 01-Nov-2014 13:41:55 GMT; path=/; domain=.flickr.com" + } + ] + }, + { + "seqno": 188, + "wire": "886196dc34fd280654d27eea0801128166e341b8db8a62d1bf5f88352398ac74acb37f0f0d836db137c1eb5892a47e561cc58190b6cb800001f55db1d0627f6496d07abe94640a681fa50401094082e32fdc682a70df7b6c96e4593e94034a436cca0801028266e08371a1298b46ffcb55856996c426bf7f2c9fc7937a92d87a54ae73a4e419272b60657968313ad8b8c8d2fe8739ceb90f4f7f2ca1c7937a92d87a54ae73a4e419272b60657968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad8195e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5525" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 10:39:41 UTC" + }, + { + "last-modified": "Wed, 04 Aug 2010 23:21:42 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "435224" + }, + { + "x-cache": "HIT from photocache503.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache503.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache503.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 189, + "wire": "88c6c50f0d8369c6c3c8f2c46496d07abe94640a681fa50401094082e083719029c37def6c96df697e94032a436cca0801028215c6dfb80794c5a37fd155850b8e09c6bf7f049fc7937a92d87a54ae73a4e419272b62697968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b62697968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89a5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4651" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 10:21:30 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:59:08 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "166264" + }, + { + "x-cache": "HIT from photocache524.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache524.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache524.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 190, + "wire": "88cccb0f0d8369969ace7f23ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcfcb6496d07abe94640a681fa5040109408ae34cdc6c4a70df7b6c96d07abe94640a436cca0801028072e01db80714c5a37fd85585640179e07f7f059fc7937a92d87a54ae73a4e419272b60717968313ad8b8c8d2fe8739ceb90f4f7f05a1c7937a92d87a54ae73a4e419272b60717968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81c5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4344" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:43:52 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:06 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "301880" + }, + { + "x-cache": "HIT from photocache506.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache506.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache506.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 191, + "wire": "88d3d20f0d83699745d5c4d16496d07abe94640a681fa5040109408ae32d5c13ca70df7b6c96df697e94032a436cca0801028215c6deb8cb6a62d1bfde55856990b6cb9f7f049fc7937a92d87a54ae73a4e419272b626d7968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b626d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89b5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4372" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:34:28 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:35 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431536" + }, + { + "x-cache": "HIT from photocache525.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache525.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache525.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 192, + "wire": "88d9d80f0d8369a69cdbcad7c36c96df697e94032a436cca0801028215c6deb8d34a62d1bfe355856990b8f3ff7f039fc7937a92d87a54ae73a4e419272b6cb4bcb4189d6c5c64697f439ce75c87a77f03a2c7937a92d87a54ae73a4e419272b6cb4bcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2d2f2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4446" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:34:28 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:44 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431689" + }, + { + "x-cache": "HIT from photocache534.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache534.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache534.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 193, + "wire": "88dedd0f0d8369a643e0cfdcc86c96e4593e94642a681d8a0801028176e34e5c69c53168dfe87f029fc7937a92d87a54ae73a4e419272b610af2d06275b17191a5fd0e739d721e9f7f02a1c7937a92d87a54ae73a4e419272b610af2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad842bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb55856990b8f83f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4431" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:34:28 UTC" + }, + { + "last-modified": "Wed, 31 Mar 2010 17:46:46 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "x-cache": "HIT from photocache511.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache511.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache511.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + }, + { + "age": "431690" + } + ] + }, + { + "seqno": 194, + "wire": "88e3e20f0d840b2f059fe5d4e16496dc34fd280654dc5ad410042504cdc68371a0a9c37def6c96d07abe940baa681fa5040081410ae001702ca98b46ffee5585700f36cb3f7f059fc7937a92d87a54ae73a4e419272b40797968313ad8b8c8d2fe8739ceb90f4f7f05a1c7937a92d87a54ae73a4e419272b40797968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad01e5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "13813" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Sat, 03 Sep 2022 23:41:41 UTC" + }, + { + "last-modified": "Mon, 17 May 2010 22:00:13 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "608533" + }, + { + "x-cache": "HIT from photocache408.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache408.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache408.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 195, + "wire": "88e9e80f0d8310020febdae7d96c96df697e94032a436cca0801028215c6deb8d894c5a37f52848fd24a8f55856990b6cbbf7f049fc7937a92d87a54ae73a4e419272b6cb6bcb4189d6c5c64697f439ce75c87a77f04a2c7937a92d87a54ae73a4e419272b6cb6bcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2daf2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2010" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:43:52 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:52 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431537" + }, + { + "x-cache": "HIT from photocache535.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache535.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache535.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 196, + "wire": "88efee0f0d8369b7dcf1e0ed6496d07abe94640a681fa5040109408ae340b82694e1bef76c96d07abe94640a436cca0801028072e01db82754c5a37fc4558464017c007f049fc7937a92d87a54ae73a4e419272b6d017968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b6d017968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cadb405e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4596" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:24 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:27 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "301900" + }, + { + "x-cache": "HIT from photocache540.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache540.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache540.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 197, + "wire": "886196dc34fd280654d27eea0801128166e341b8db8a62d1bf5f88352398ac74acb37f0f0d83699799408721eaa8a4498f5788ea52d6b0e83772ffe95892a47e561cc58190b6cb800001f55db1d0627f6496d07abe94640a681fa5040109408ae340b826d4e1bef76c96df697e94032a436cca0801028215c6deb8d854c5a37fcec6c5c4558479f71e17", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4383" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:25 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:51 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "x-cache": "HIT from photocache540.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache540.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache540.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + }, + { + "age": "89682" + } + ] + }, + { + "seqno": 198, + "wire": "88c4c30f0d8365b007c2edc16496d07abe94640a681fa5040109403d7002b810a9c37def6c96df697e94032a436cca0801028215c6dfb816d4c5a37fd1558479f71d67ecebea", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3501" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 08:02:11 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:59:15 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "89673" + }, + { + "x-cache": "HIT from photocache506.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache506.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache506.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 199, + "wire": "88c7c60f0d8365e13fc5f0c46496d07abe94640a681fa5040109408ae340b810a9c37def6c96e4593e94034a436cca0801028266e083704ca98b46ffd4558579d0b8d39f7f0e9fc7937a92d87a54ae73a4e419272b62657968313ad8b8c8d2fe8739ceb90f4f7f0ea1c7937a92d87a54ae73a4e419272b62657968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad8995e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3829" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:11 UTC" + }, + { + "last-modified": "Wed, 04 Aug 2010 23:21:23 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "871646" + }, + { + "x-cache": "HIT from photocache523.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache523.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache523.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 200, + "wire": "88cdcc0f0d8365d783cb4003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcfcb6496d07abe94640a681fa50401094102e34e5c03aa70df7b6c96d07abe94640a436cca0801028072e34fdc032a62d1bfdb55856996c4267f7f059fc7937a92d87a54ae73a4e419272b6012f2d06275b17191a5fd0e739d721e9f7f05a1c7937a92d87a54ae73a4e419272b6012f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad804bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3781" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 20:46:07 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:49:03 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "435223" + }, + { + "x-cache": "HIT from photocache502.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache502.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache502.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 201, + "wire": "88d4d30f0d83682f39d2c4d16496d07abe94640a681fa50401094102e341b8c854e1bef7d0e0558565c65f03ff7f039fc7937a92d87a54ae73a4e419272b62697968313ad8b8c8d2fe8739ceb90f4f7f03a1c7937a92d87a54ae73a4e419272b62697968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89a5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4186" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 20:41:31 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:51 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "363909" + }, + { + "x-cache": "HIT from photocache524.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache524.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache524.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 202, + "wire": "88d9d80f0d8365e0b5d7c9d66496d07abe94640a681fa5040109408ae34cdc6c4a70df7bd2e57f029fc7937a92d87a54ae73a4e419272b6cbabcb4189d6c5c64697f439ce75c87a77f02a2c7937a92d87a54ae73a4e419272b6cbabcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2eaf2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeffd7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3814" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:43:52 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:59:15 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "x-cache": "HIT from photocache537.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache537.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache537.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + }, + { + "age": "89682" + } + ] + }, + { + "seqno": 203, + "wire": "88dddc0f0d83134ebddbcddad96c96df697e94032a436cca0801028215c6deb810a98b46ffe955856990b6cb9f7f039fc7937a92d87a54ae73a4e419272b610af2d06275b17191a5fd0e739d721e9f7f03a1c7937a92d87a54ae73a4e419272b610af2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad842bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2478" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:25 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:11 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431536" + }, + { + "x-cache": "HIT from photocache511.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache511.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache511.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 204, + "wire": "88e2e10f0d8375979de0d2df6496d07abe94640a681fa5040109408ae32d5c13ca70df7b6c96e4593e94642a681d8a0801028176e34edc038a62d1bfef55856990bacbffd8d7d6", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "7387" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:34:28 UTC" + }, + { + "last-modified": "Wed, 31 Mar 2010 17:47:06 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431739" + }, + { + "x-cache": "HIT from photocache523.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache523.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache523.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 205, + "wire": "88e5e40f0d836de741e3d5e26496d07abe94640a681fa5040109408ae01ab81029c37def6c96d07abe94640a436cca0801028072e01db81754c5a37ff2558565975f65ef7f079fc7937a92d87a54ae73a4e419272b626d7968313ad8b8c8d2fe8739ceb90f4f7f07a1c7937a92d87a54ae73a4e419272b626d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89b5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5870" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:04:10 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:17 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "337938" + }, + { + "x-cache": "HIT from photocache525.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache525.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache525.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 206, + "wire": "88ebea0f0d836c4017e9dbe86496d07abe94640a681fa5040109408ae340b81129c37def6c96df697e94032a436cca0801028215c6ddb8d894c5a37f52848fd24a8f55856990bacbdf7f059fc7937a92d87a54ae73a4e419272b606d7968313ad8b8c8d2fe8739ceb90f4f7f05a1c7937a92d87a54ae73a4e419272b606d7968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad81b5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "5202" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:40:12 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:57:52 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431738" + }, + { + "x-cache": "HIT from photocache505.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache505.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache505.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 207, + "wire": "88f2f10f0d8313c207f0e2efcd6c96df697e94032a436cca0801028215c6deb8c854c5a37fc3d27f029fc7937a92d87a54ae73a4e419272b6cbcbcb4189d6c5c64697f439ce75c87a77f02a2c7937a92d87a54ae73a4e419272b6cbcbcb4189d6c5c64697f439ce75c87a6e3ccff7cae0ae152b9ce9390649cadb2f2f2d06275b17191a5fd0e739d721e9b8f32a7f48ed69a4604bbabeedf0ddcf81ffeff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "2820" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:34:28 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:31 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "431536" + }, + { + "x-cache": "HIT from photocache538.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache538.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache538.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 208, + "wire": "886196dc34fd280654d27eea0801128166e341b8db8a62d1bf5f88352398ac74acb37f0f0d83640d35408721eaa8a4498f5788ea52d6b0e83772ffe95892a47e561cc58190b6cb800001f55db1d0627fde6c96d07abe94640a436cca0801028072e01db8c814c5a37fcb7f069fc7937a92d87a54ae73a4e419272b61717968313ad8b8c8d2fe8739ceb90f4f7f06a1c7937a92d87a54ae73a4e419272b61717968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad85c5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff5583132e33", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3044" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:43:52 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:07:30 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "x-cache": "HIT from photocache516.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache516.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache516.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + }, + { + "age": "2363" + } + ] + }, + { + "seqno": 209, + "wire": "88c6c50f0d8369e65ac4efc3da6c96df697e94032a436cca0801028215c6dfb80714c5a37fd0558479f71e177f049fc7937a92d87a54ae73a4e419272b6212f2d06275b17191a5fd0e739d721e9f7f04a1c7937a92d87a54ae73a4e419272b6212f2d06275b17191a5fd0e739d721e9b8f337cad0ae152b9ce9390649cad884bcb4189d6c5c64697f439ce75c87a6e3cca9fd23b5a691812eeafbb7c3773e07ffb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "4834" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 12:34:28 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:59:06 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "89682" + }, + { + "x-cache": "HIT from photocache522.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache522.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache522.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 210, + "wire": "88cbca0f0d8365971bc94003703370ff2bacf4189eac2cb07f33a535dc61835529d7f439ce75c87a58f0c918ad9ad7f34d1fcfd297b5c1fcde875297f76b52f6adaa5ee1b5486fe852fe0e2a6f87229af742a6bdd7d4c9c61329938df3297b569329bf0673a9ab7eb329ab86d52fe0ce653743a0ca6adfb4ca70d3b4ca6be174ca64d37d4d78f9a9ab4e753869c8a6be1b54c3934a97b568534c3c54c9a77a97f06852f69dea6edf0a9af567531e0854d7b70299f55e5316ae3fcfc96496d07abe94640a681fa50401094082e36e5c03aa70df7b6c96df697e94032a436cca0801028215c6deb8d38a62d1bfd75584136268417f059fc7937a92d87a54ae73a4e419272b62797968313ad8b8c8d2fe8739ceb90f4f7f05a1c7937a92d87a54ae73a4e419272b62797968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cad89e5e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3365" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 10:56:07 UTC" + }, + { + "last-modified": "Tue, 03 Aug 2010 22:58:46 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "252421" + }, + { + "x-cache": "HIT from photocache528.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache528.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache528.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 211, + "wire": "88d2d10f0d8365f75ed0c4cf6496d07abe94640a681fa50401094082e32fdc682a70df7b6c96d07abe94640a436cca0801028072e34fdc1014c5a37fdd558579d101d7bf7f049fc7937a92d87a54ae73a4e419272b6c897968313ad8b8c8d2fe8739ceb90f4f7f04a1c7937a92d87a54ae73a4e419272b6c897968313ad8b8c8d2fe8739ceb90f4dc7997cae0ae152b9ce9390649cadb225e5a0c4eb62e3234bfa1ce73ae43d371e654fe91dad348c097757ddbe1bb9f03ffdff", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "3978" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "max-age=315360000,public" + }, + { + "expires": "Mon, 30 May 2022 10:39:41 UTC" + }, + { + "last-modified": "Mon, 30 Aug 2010 06:49:20 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "age": "872078" + }, + { + "x-cache": "HIT from photocache532.flickr.ac4.yahoo.com" + }, + { + "x-cache-lookup": "HIT from photocache532.flickr.ac4.yahoo.com:83" + }, + { + "via": "1.1 photocache532.flickr.ac4.yahoo.com:83 (squid/2.7.STABLE9)" + } + ] + }, + { + "seqno": 212, + "wire": "88d8ca5895aec3771a4bf4a54759093d85fa5291f9587316007f7b8b84842d695b05443c86aa6f7f19842507417f798624f6d5d4b27f5f911d75d0620d263d4c795ba0fb8d04b0d5a75a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:56 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "private, no-store, max-age=0" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 213, + "wire": "886196dc34fd280654d27eea0801128166e341b8dbaa62d1bfd10f28c332505420c7a8d20400005b702cbef38ebf00f8761fba3d6818ffe4a82a0200002db8165f79c75f83ed4ac699e063ed490f48cd540b8ea1d1e9262217f439ce75c87a7f400274738a028cba2524232cc551434085aec1cd48ff86a8eb10649cbf6496dc34fd280654d27eea0801128166e341b8dbca62d1bf5899a8eb10649cbf4a5761bb8d25fa529b5095ac2f71d0690692ff0f0d023436ec408b4d8327535532c848d36a3f8b96b2288324aa26c193a964c8c7c5", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:57 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "set-cookie": "itsessionid10001561398679=aUqazlyMaa|fses10001561398679=; path=/; domain=.analytics.yahoo.com" + }, + { + "ts": "0 372 dc33_ne1" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:58 GMT" + }, + { + "cache-control": "no-cache, private, must-revalidate" + }, + { + "content-length": "46" + }, + { + "accept-ranges": "bytes" + }, + { + "tracking-status": "fpc site tracked" + }, + { + "vary": "Accept-Encoding" + }, + { + "connection": "close" + }, + { + "content-type": "application/x-javascript" + } + ] + }, + { + "seqno": 214, + "wire": "48826402c47689e7bf73015c405c2cff408ff2b5869a74d2590c35a73a1350e92f8db075a4f601c7195eca578e50ff7f1ad2acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725f52f70da3521bfa06a5fc1c46a6bdd08d4d7baf8d4d5c36a97786e52f6ad0a64d3bd4d5bef29af86d5376f87f9f0f1fa29d29aee30c0e45fd18b44948ea1cc5b1721e962b3792d1fe1a4819744003ff09805f58a1a8eb10649cbf4a54759093d85fa529b5095ac2f71d0690692fd2948fcac398b0037b012a6c96dc34fd280654d27eea0801128166e341b8dbaa62d1bf6496dc34fd280654d27eea0801128166e341b8dbaa62d1bfc9cc550130cfeb", + "headers": [ + { + ":status": "302" + }, + { + "date": "Sat, 03 Nov 2012 13:41:57 GMT" + }, + { + "server": "YTS/1.20.13" + }, + { + "x-rightmedia-hostname": "raptor0663.rm.bf1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID CURa ADMa DEVa PSAa PSDa OUR BUS COM INT OTC PUR STA\"" + }, + { + "location": "http://ad.yieldmanager.com/pixel?id=372009&t=2" + }, + { + "cache-control": "no-cache, no-store, must-revalidate, max-age=0" + }, + { + "vary": "*" + }, + { + "last-modified": "Sat, 03 Nov 2012 13:41:57 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:57 GMT" + }, + { + "pragma": "no-cache" + }, + { + "content-encoding": "gzip" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 215, + "wire": "88ccdf5894a8eb10649cbf4a54759093d85fa52bb0ddc692ffcb0f0d023433d15f87352398ac4c697f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:57 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 216, + "wire": "88cee1bfcc0f0d023433d2be", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:57 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 217, + "wire": "88cee1bfcc0f0d023433d2be", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:57 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 218, + "wire": "886196dc34fd280654d27eea0801128166e341b8dbca62d1bfe2c0cd0f0d023433d3bf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:41:58 GMT" + }, + { + "p3p": "policyref=\"http://info.yahoo.com/w3c/p3p.xml\", CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV\"" + }, + { + "cache-control": "no-cache, no-store, private" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_28.json b/http/http-client/src/test/resources/hpack-test-case/story_28.json new file mode 100644 index 000000000..0acee77b5 --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_28.json @@ -0,0 +1,5549 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "488264010f1f929d29aee30c78f1e17a0d5752c86a9721e9630f0d01306196dc34fd280654d27eea0801128166e059b8cbea62d1bf7686a0d34e94d727", + "headers": [ + { + ":status": "301" + }, + { + "location": "http://www.linkedin.com/" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:13:39 GMT" + }, + { + "server": "lighttpd" + } + ] + }, + { + "seqno": 1, + "wire": "88768c86b19272ad78fe8e92b015c30f288afc5b3e45b25fbd05e0ff4003703370f5bdae0fe6f43a94bfbb5a97b56d52f70daa437f4194bf838994df0e4329af7426535eebe65327184ca64e37cca5ed5a4ca6ae1b54bf833994dd0e8329c34ed329af85d329ab7ed329934df535e3e6a6ad39d4e1a7229af86d530e4d2a5ed5a14d30f153269dea5fc1a14bda77a9af567535edc1fcff4087f2b5065adb4d2794c02f7de9db4f49f9e0c396bf2ee22ebc564d041f408bf2b4b60e92ac7ad263d48f89dd0e8c1ab6e4c5934f7b8b84842d695b05443c86aa6f4085aec1cd48ff86a8eb10649cbf6496df3dbf4a002a651d4a05f740a0017000b800298b46ff5886a8eb2127b0bf5f91497ca589d34d1f649c7620a98386fc2b3d5b842d4b70ddc9550130798624f6d5d4b27f408721eaa8a4498f5788ea52d6b0e83772ff5a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "X-LI-IDC=C1" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-li-uuid": "E2zvmRmjhYEFJpx7GePGrg==" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:13:39 GMT" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 2, + "wire": "88cb54919d29aee30c78f1e17a0d5752c86a9721e96c96df697e94640a6a2254100225042b8d337190a98b46ff5f8b497ca58e83ee3412c3569fcac10f0d8368426b4084f2b124ab04414b414d588ca47e561cc58190884d3c217f6496e4593e94640a6a22541002ca8215c69db82654c5a37f6196dc34fd280654d27eea0801128166e059b8d054c5a37fc6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Tue, 30 Oct 2012 22:43:31 GMT" + }, + { + "content-type": "text/javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "4224" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=31224822" + }, + { + "expires": "Wed, 30 Oct 2013 22:47:23 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:41 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 3, + "wire": "88d2c45f86497ca582211fc6cf0f0d830badbbc2588ca47e561cc5804cbee89c759f6496df3dbf4a01e521b6650400b2a001702f5c0b4a62d1bfc1c9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "content-type": "text/css" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "1757" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=23972673" + }, + { + "expires": "Thu, 08 Aug 2013 00:18:14 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:41 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 4, + "wire": "88d5c76c96d07abe9413ea6a225410022502edc0bb719694c5a37fc6c9d20f0d830840d7c5588ca47e561cc58190842f81d0ff6496df697e9413ea6a22541002ca8176e09ab8d894c5a37fc4cc", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Mon, 29 Oct 2012 17:17:34 GMT" + }, + { + "content-type": "text/javascript" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "1104" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=31119071" + }, + { + "expires": "Tue, 29 Oct 2013 17:24:52 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:41 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 5, + "wire": "88d8ca6c96df697e94640a6a2254100225042b8d337190298b46ffc4d5cc0f0d8369d137c8588ca47e561cc58190884d81f07f6496e4593e94640a6a22541002ca8215c6c371b0a98b46ffc7cf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Tue, 30 Oct 2012 22:43:30 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "4725" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=31225090" + }, + { + "expires": "Wed, 30 Oct 2013 22:51:51 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:41 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 6, + "wire": "88dbcd6c96df697e94640a6a2254100225042b8d33704fa98b46ffc7d8cf0f0d840bcdb21fcb588ca47e561cc58190884d89e7bf6496e4593e94640a6a22541002ca8215c6dbb807d4c5a37fcad2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Tue, 30 Oct 2012 22:43:29 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "18531" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=31225288" + }, + { + "expires": "Wed, 30 Oct 2013 22:55:09 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:41 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 7, + "wire": "88ded0c3ceda7f1d94e7affdbfa9ff6e78db93c4adc9b33de4430c3041d20f0d84782d38d7ce588ca47e561cc58190884d3c00ff6496e4593e94640a6a22541002ca8215c69db801298b46ffcdd5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Tue, 30 Oct 2012 22:43:30 GMT" + }, + { + "content-type": "text/javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-li-uuid": "YP+9O9z6wRIwf5dQLCsAAA==" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "81464" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=31224801" + }, + { + "expires": "Wed, 30 Oct 2013 22:47:02 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:41 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 8, + "wire": "88e10f288fce12c0d3ed9631beefda958d33c0c7e07f01939b3a49e35038f8f397633811db1fa4430c3041dfdedd640130dc5f87352398ac4c697fdb6196dc34fd280654d27eea0801128166e059b8d32a62d1bf550131dbdad95886a8eb10649cbf", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "L1e=495eba97; path=/" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-li-uuid": "gLtcwO0VwxJQ3EsqHysAAA==" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "pragma": "no-cache" + }, + { + "expires": "0" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "image/gif" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:13:43 GMT" + }, + { + "age": "1" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + } + ] + }, + { + "seqno": 9, + "wire": "88e7d96c96df3dbf4a09b535112a080112817ee01eb806d4c5a37f5f89352398ac7958c43d5fe5c8dc0f0d03333631d8588ca47e561cc5819085e780d07f6496e4593e94640a6a22541002ca8115c65ab82694c5a37fc4df", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Thu, 25 Oct 2012 19:08:05 GMT" + }, + { + "content-type": "image/x-icon" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-li-uuid": "YP+9O9z6wRIwf5dQLCsAAA==" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "361" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=31188041" + }, + { + "expires": "Wed, 30 Oct 2013 12:34:24 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 10, + "wire": "88ebdd6c96e4593e94642a6a225410022502fdc086e05a53168dff5f87352398ac5754dfe97f0a93bd7a4aaa96feff13e5f1469bdc5f31e1861820e1588ca47e561cc58190b4e32d34d76496dc34fd280129a4fdd41002ca8176e01ab82794c5a37f6196dc34fd280654d27eea0801128166e059b8d34a62d1bf0f0d8365a79fe5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Wed, 31 Oct 2012 19:11:14 GMT" + }, + { + "content-type": "image/png" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-li-uuid": "CCdnnfDTwhJwlNCV9ioAAA==" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "max-age=31463444" + }, + { + "expires": "Sat, 02 Nov 2013 17:04:28 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:44 GMT" + }, + { + "content-length": "3489" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 11, + "wire": "88f1e36c96d07abe940b2a436cca080112817ee019b8cbca62d1bfc3eee50f0d83081c17588ca47e561cc5804d34e899133f6496df697e940b2a436cca08016540bf700ddc69d53168dfc1e8", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Mon, 13 Aug 2012 19:03:38 GMT" + }, + { + "content-type": "image/png" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1062" + }, + { + "cache-control": "max-age=24472323" + }, + { + "expires": "Tue, 13 Aug 2013 19:05:47 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:44 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 12, + "wire": "887684aa6355e7c2cfeae94088ea52d6b0e83772ff8749a929ed4c02076496df3dbf4a002a5f29140befb4a05cb8005c0014c5a37ff2ce7f37d2acf4189eac2cb07f33a535dc618f1e3c2e6a6cf07b2893c1a42ae43d2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725f535ee85486fe853570daa64d37d4e1a7229a61e2a5ed5a3f9f", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx" + }, + { + "date": "Sat, 03 Nov 2012 13:13:44 GMT" + }, + { + "content-type": "image/gif" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "keep-alive": "timeout=20" + }, + { + "expires": "Thu, 01 Dec 1994 16:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.imrworldwide.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND UNI NAV COM\"" + } + ] + }, + { + "seqno": 13, + "wire": "880f0d840b4f3cf7eb6c96d07abe941094d444a820044a05bb8d86e05f53168dff4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb6196dc34fd280654d27eea0801128072e059b827d4c5a37f6496dc34fd280654d27eea080112817ae059b827d4c5a37fecf8558413620b7f5890a47e561cc581a644007d295db1d0627f7686c58703025c1f", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "14888" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Mon, 22 Oct 2012 15:51:19 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "date": "Sat, 03 Nov 2012 06:13:29 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 18:13:29 GMT" + }, + { + "content-type": "text/javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "age": "25215" + }, + { + "cache-control": "max-age=43200, public" + }, + { + "server": "GFE/2.0" + } + ] + }, + { + "seqno": 14, + "wire": "880f0d023335f26c96e4593e941054ca3a941000d2817ee361b8c814c5a37fc46196df3dbf4a002a693f7504008940b571905c03ea62d1bf6496e4593e940bea435d8a080002810dc699b800298b46ffdcfe55850b8f082e7f58a9aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52bb0fe7d2d617b8e83483497fc34085aec1cd48ff86a8eb10649cbf", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "35" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Wed, 21 Jan 2004 19:51:30 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "date": "Thu, 01 Nov 2012 14:30:09 GMT" + }, + { + "expires": "Wed, 19 Apr 2000 11:43:00 GMT" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "age": "168216" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, proxy-revalidate" + }, + { + "server": "GFE/2.0" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 15, + "wire": "88c20f0d023335c9bec1c3dfbfc0c4", + "headers": [ + { + ":status": "200" + }, + { + "date": "Thu, 01 Nov 2012 14:30:09 GMT" + }, + { + "content-length": "35" + }, + { + "x-content-type-options": "nosniff" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Wed, 19 Apr 2000 11:43:00 GMT" + }, + { + "last-modified": "Wed, 21 Jan 2004 19:51:30 GMT" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, proxy-revalidate" + }, + { + "age": "168216" + }, + { + "server": "GFE/2.0" + } + ] + }, + { + "seqno": 16, + "wire": "887f3a842507417f7b8b84842d695b05443c86aa6ffa6c96dc34fd280656d27eeb0801128166e059b8d36a62d1bf0f1388d0058058dd6e51395f911d75d0620d263d4c795ba0fb8d04b0d5a758a7aec3771a4bf4a54759360ea44a7b29fa529b5095ac2f71d0690692fd2948fcac398b038069e0036496dc34fd281029a4fdd410022502cdc0b371a6d4c5a37f0f0d83109f7b6196dc34fd280654d27eea0801128166e059b8d36a62d1bf76025153", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Sat, 03-Nov-2012 13:13:45 GMT" + }, + { + "etag": "M0-0eb75f26" + }, + { + "content-type": "application/x-javascript" + }, + { + "cache-control": "private, no-transform, must-revalidate, max-age=604800" + }, + { + "expires": "Sat, 10 Nov 2012 13:13:45 GMT" + }, + { + "content-length": "2298" + }, + { + "date": "Sat, 03 Nov 2012 13:13:45 GMT" + }, + { + "server": "QS" + } + ] + }, + { + "seqno": 17, + "wire": "88c5e70f28caa4903607db0bcf3eb32d3320d60b92ba558657db17da85f359ac2a20d07abe94036b681fa58400b4a059b8166e34da98b46ffb52b1a67818fb5243d2335502fdad1d49416cee55c87a7f7f14b7bdae0fe74eac8a5fddad4bdab6a9a725f52f70da3521bfa06a5fc1c46a6bdd09d4d7baf9d4d5c36a9ba1d0353269bea5ed5a14d30f1fe758a1aec3771a4bf4a547588324e5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfc86496c361be94034a436cca05f75e5022b8005c0014c5a37f0f0d023335c2c1", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + }, + { + "set-cookie": "mc=50951889-343da-16f7e-ae952; expires=Mon, 05-May-2014 13:13:45 GMT; path=/; domain=.quantserve.com" + }, + { + "p3p": "CP=\"NOI DSP COR NID CURa ADMa DEVa PSAo PSDo OUR SAMa IND COM NAV\"" + }, + { + "cache-control": "private, no-cache, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 04 Aug 1978 12:00:00 GMT" + }, + { + "content-length": "35" + }, + { + "date": "Sat, 03 Nov 2012 13:13:45 GMT" + }, + { + "server": "QS" + } + ] + }, + { + "seqno": 18, + "wire": "88c5c75a839bd9ab6496dc34fd281754d27eea0801128166e059b8d36a62d1bfc40f0d83085a077f0b88ea52d6b0e83772ff589caec3771a4bf4a54759360ea44a7b29fa5291f958731600880fb8007f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Sat, 17 Nov 2012 13:13:45 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:45 GMT" + }, + { + "content-length": "1140" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-transform, max-age=1209600" + } + ] + }, + { + "seqno": 19, + "wire": "89c9cbc16496d07abe940054ca3a940bef814002e001700053168dffc70f0d0130c058b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfcf", + "headers": [ + { + ":status": "204" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:45 GMT" + }, + { + "content-length": "0" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 20, + "wire": "88768c86b19272ad78fe8e92b015c30f28aea0754d07f3de017c503aa680b52d6a3f9fb53896c418f5401fb52f9e919aa828355d4b21aa5c87a7ed4d634cf0317f08f5bdae0fe6f43a94bfbb5a97b56d52f70daa437f4194bf838994df0e4329af7426535eebe65327184ca64e37cca5ed5a4ca6ae1b54bf833994dd0e8329c34ed329af85d329ab7ed329934df535e3e6a6ad39d4e1a7229af86d530e4d2a5ed5a14d30f153269dea5fc1a14bda77a9af567535edc1fcff7f2994e935b77cdff37b5bd0b284d9b7839ec1bbc4107f408bf2b4b60e92ac7ad263d48f89dd0e8c1ab6e4c5934fd1d3f55886a8eb2127b0bf5f91497ca589d34d1f649c7620a98386fc2b3d5b842d4b70dd6196dc34fd280654d27eea0801128166e059b8d3ea62d1bf550130798624f6d5d4b27fcbcdf74087f2b4a85adb4d279778a08de091a648d099948065d8c520e40b6c8c92b4d47f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lang=\"v=2&lang=en-us\"; Version=1; Domain=linkedin.com; Path=/" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-li-uuid": "jguBxDxCP8A3strRU6z0Sw==" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "pragma": "no-cache" + }, + { + "expires": "0" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:13:49 GMT" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "x-fs-uuid": "8e0b81c43c423fc037b2dad153acf44b" + } + ] + }, + { + "seqno": 21, + "wire": "887686a0d34e94d7270f28aea0754d07f3de017c503aa680b52d6a3f9fb53896c418f5401fb52f9e919aa828355d4b21aa5c87a7ed4d634cf031c8c7c6d9db640130c6f8c4c3c2c1ced0fac052848fd24a8f0f138afe42e3a2136f09d77f9f6c96e4593e941094cb6d4a08010a8205c13d700053168dff0f0d83085b07", + "headers": [ + { + ":status": "200" + }, + { + "server": "lighttpd" + }, + { + "set-cookie": "lang=\"v=2&lang=en-us\"; Version=1; Domain=linkedin.com; Path=/" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-li-uuid": "jguBxDxCP8A3strRU6z0Sw==" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "pragma": "no-cache" + }, + { + "expires": "0" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "image/x-icon" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:13:49 GMT" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "x-fs-uuid": "8e0b81c43c423fc037b2dad153acf44b" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"1672258277\"" + }, + { + "last-modified": "Wed, 22 Jun 2011 20:28:00 GMT" + }, + { + "content-length": "1150" + } + ] + }, + { + "seqno": 22, + "wire": "88cc54919d29aee30c78f1e17a0d5752c86a9721e96c96df3dbf4a05c521b6650400894006e09ab8d094c5a37ff8ded40f0d83081a6b588ca47e561cc5804d3a20882fff6496c361be940b8a436cca08016540b9702d5c03ea62d1bf6196dc34fd280654d27eea0801128166e059b8d814c5a37fd5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Thu, 16 Aug 2012 01:24:42 GMT" + }, + { + "content-type": "image/png" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1044" + }, + { + "cache-control": "max-age=24721219" + }, + { + "expires": "Fri, 16 Aug 2013 16:14:09 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:50 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 23, + "wire": "88d1c26c96e4593e940baa6a225410022504cdc03f71b1298b46ff5f86497ca582211fe3d90f0d837c2e3d588ca47e561cc5819005c79965bf6496c361be940bca6a22541002ca8176e05fb826d4c5a37fc2d9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Wed, 17 Oct 2012 23:09:52 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "9168" + }, + { + "cache-control": "max-age=30168335" + }, + { + "expires": "Fri, 18 Oct 2013 17:19:25 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:50 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 24, + "wire": "88f76196dc34fd280654d27eea0801128166e059b8d854c5a37f5f87352398ac4c697fcedbf8f7e95886a8eb10649cbff7", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx" + }, + { + "date": "Sat, 03 Nov 2012 13:13:51 GMT" + }, + { + "content-type": "image/gif" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "keep-alive": "timeout=20" + }, + { + "expires": "Thu, 01 Dec 1994 16:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.imrworldwide.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND UNI NAV COM\"" + } + ] + }, + { + "seqno": 25, + "wire": "88e9bfe0eadf0f0d023335c0e2", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "private, no-cache, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 04 Aug 1978 12:00:00 GMT" + }, + { + "content-length": "35" + }, + { + "date": "Sat, 03 Nov 2012 13:13:51 GMT" + }, + { + "server": "QS" + } + ] + }, + { + "seqno": 26, + "wire": "880f0d023335deeff5eeedbfe855840b8f0842ecf1eb", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "35" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Wed, 21 Jan 2004 19:51:30 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "date": "Thu, 01 Nov 2012 14:30:09 GMT" + }, + { + "expires": "Wed, 19 Apr 2000 11:43:00 GMT" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "age": "168222" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, proxy-revalidate" + }, + { + "server": "GFE/2.0" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 27, + "wire": "89e7e9dfdbc10f0d0130dddaeb", + "headers": [ + { + ":status": "204" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:51 GMT" + }, + { + "content-length": "0" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 28, + "wire": "88d9ca6c96c361be940094d27eea080112817ae041719714c5a37f5f87352398ac5754dfeb7f1a942c3ef479d3c3478f397c624fae2f98f0c30c107fe2588ca47e561cc58190b626df7d9f6496dd6d5f4a0195349fba8200595020b8276e01a53168dfc60f0d821099e2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Fri, 02 Nov 2012 18:10:36 GMT" + }, + { + "content-type": "image/png" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-li-uuid": "eAzMxNUMwxJwGtyV9ioAAA==" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "max-age=31525993" + }, + { + "expires": "Sun, 03 Nov 2013 10:27:04 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:51 GMT" + }, + { + "content-length": "223" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 29, + "wire": "88decf6c96dc34fd2800a9b8b5a820044a00171b7ae01f53168dffc2ef7f0293bd7a4aaa96feff13e5f1469bdc5f31e1861820e6588ca47e561cc5804e01a79e745f6496dd6d5f4a002a6e2d6a0801654006e00371a654c5a37fca0f0d840842e03fe6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Sat, 01 Sep 2012 00:58:09 GMT" + }, + { + "content-type": "image/png" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-li-uuid": "CCdnnfDTwhJwlNCV9ioAAA==" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "max-age=26048872" + }, + { + "expires": "Sun, 01 Sep 2013 01:01:43 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:51 GMT" + }, + { + "content-length": "11160" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 30, + "wire": "88e20f28aea0754d07f3de017c503aa680b52d6a3f9fb53896c418f5401fb52f9e919aa828355d4b21aa5c87a7ed4d634cf031e17f019487f76c7936bfc5c7a5bbb28e7fbef61f707c4107e0f3f5d7dfdedd6196dc34fd280654d27eea0801128166e059b8db4a62d1bfdcdbe8eaca7f1b96005f69b8c410cadb658c8e902d09b702f92400c2291dd80f138afe42e3a2136f09d77f9fd70f0d8369d0b5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lang=\"v=2&lang=en-us\"; Version=1; Domain=linkedin.com; Path=/" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-li-uuid": "AZRbIR9V68fBQlYZzQoS1w==" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "pragma": "no-cache" + }, + { + "expires": "0" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:13:54 GMT" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "x-fs-uuid": "01945b211f55ebc7c1425619cd0a12d7" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"1672258277\"" + }, + { + "last-modified": "Wed, 22 Jun 2011 20:28:00 GMT" + }, + { + "content-length": "4714" + } + ] + }, + { + "seqno": 31, + "wire": "88f6ccedf7ec0f0d023335bfef", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "private, no-cache, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 04 Aug 1978 12:00:00 GMT" + }, + { + "content-length": "35" + }, + { + "date": "Sat, 03 Nov 2012 13:13:54 GMT" + }, + { + "server": "QS" + } + ] + }, + { + "seqno": 32, + "wire": "887684aa6355e7c0cdddea4088ea52d6b0e83772ff8749a929ed4c02076496df3dbf4a002a5f29140befb4a05cb8005c0014c5a37fface7f28d2acf4189eac2cb07f33a535dc618f1e3c2e6a6cf07b2893c1a42ae43d2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725f535ee85486fe853570daa64d37d4e1a7229a61e2a5ed5a3f9f", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx" + }, + { + "date": "Sat, 03 Nov 2012 13:13:54 GMT" + }, + { + "content-type": "image/gif" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "keep-alive": "timeout=20" + }, + { + "expires": "Thu, 01 Dec 1994 16:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.imrworldwide.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND UNI NAV COM\"" + } + ] + }, + { + "seqno": 33, + "wire": "880f0d023335ef6c96e4593e941054ca3a941000d2817ee361b8c814c5a37f4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb6196df3dbf4a002a693f7504008940b571905c03ea62d1bf6496e4593e940bea435d8a080002810dc699b800298b46ffd47b8b84842d695b05443c86aa6f55850b8f084e7f58a9aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52bb0fe7d2d617b8e83483497f7686c58703025c1f4085aec1cd48ff86a8eb10649cbf", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "35" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Wed, 21 Jan 2004 19:51:30 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "date": "Thu, 01 Nov 2012 14:30:09 GMT" + }, + { + "expires": "Wed, 19 Apr 2000 11:43:00 GMT" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "age": "168226" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, proxy-revalidate" + }, + { + "server": "GFE/2.0" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 34, + "wire": "895f911d75d0620d263d4c795ba0fb8d04b0d5a7c3f9f5cd0f0d0130f7f4bf", + "headers": [ + { + ":status": "204" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:54 GMT" + }, + { + "content-length": "0" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 35, + "wire": "88f30f28aea0754d07f3de017c503aa680b52d6a3f9fb53896c418f5401fb52f9e919aa828355d4b21aa5c87a7ed4d634cf031f27f0f94f19c3af226bc7b899eeeda01af8f5367cfb2083ff1c4c0e8f0efee6196dc34fd280654d27eea0801128166e059b8dbea62d1bfedecf9fbdb7f0f97202391a9442906d3ad3e41102d38dc8095b71a79e8c527e90f138afe42e3a2136f09d77f9fe80f0d8369d0b5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "lang=\"v=2&lang=en-us\"; Version=1; Domain=linkedin.com; Path=/" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-li-uuid": "wL1PItpHScLBRl0PVkiLLQ==" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "pragma": "no-cache" + }, + { + "expires": "0" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:13:59 GMT" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "x-fs-uuid": "c0bd4f22da4749c2c1465d0f56488b2d" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"1672258277\"" + }, + { + "last-modified": "Wed, 22 Jun 2011 20:28:00 GMT" + }, + { + "content-length": "4714" + } + ] + }, + { + "seqno": 36, + "wire": "88cebfddedfacdccc2dccb", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx" + }, + { + "date": "Sat, 03 Nov 2012 13:13:59 GMT" + }, + { + "content-type": "image/gif" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "keep-alive": "timeout=20" + }, + { + "expires": "Thu, 01 Dec 1994 16:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.imrworldwide.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND UNI NAV COM\"" + } + ] + }, + { + "seqno": 37, + "wire": "887f3b842507417fde58a1aec3771a4bf4a547588324e5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfc46496c361be94034a436cca05f75e5022b8005c0014c5a37f0f0d023335c276025153", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "private, no-cache, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 04 Aug 1978 12:00:00 GMT" + }, + { + "content-length": "35" + }, + { + "date": "Sat, 03 Nov 2012 13:13:59 GMT" + }, + { + "server": "QS" + } + ] + }, + { + "seqno": 38, + "wire": "880f0d0233355a839bd9abcfcecdcce2cb55850b8f09907fcac9c8", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "35" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Wed, 21 Jan 2004 19:51:30 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "date": "Thu, 01 Nov 2012 14:30:09 GMT" + }, + { + "expires": "Wed, 19 Apr 2000 11:43:00 GMT" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "age": "168230" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, proxy-revalidate" + }, + { + "server": "GFE/2.0" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 39, + "wire": "88e3ccbf6496d07abe940054ca3a940bef814002e001700053168dffc60f0d0234337f0588ea52d6b0e83772ff58b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfcb", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:13:59 GMT" + }, + { + "content-length": "43" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 40, + "wire": "88768c86b19272ad78fe8e92b015c30f28bf45107f321682a4aa525fe7ed4e25b1063d5007ed4d03f2b43316007da983cd66b0a8837cf6fd2800ad94752c17dd028005c002e040a62d1bfed4d634cf031f7f16f5bdae0fe6f43a94bfbb5a97b56d52f70daa437f4194bf838994df0e4329af7426535eebe65327184ca64e37cca5ed5a4ca6ae1b54bf833994dd0e8329c34ed329af85d329ab7ed329934df535e3e6a6ad39d4e1a7229af86d530e4d2a5ed5a14d30f153269dea5fc1a14bda77a9af567535edc1fcff7f0c95effb8effadbd2d36cbdd67fdbbefd79c5dac9a083f408bf2b4b60e92ac7ad263d48f89dd0e8c1ab6e4c5934fd3cff75886a8eb2127b0bf5f91497ca589d34d1f649c7620a98386fc2b3d5b842d4b70dd6196dc34fd280654d27eea0801128166e05ab800a98b46ff550133798624f6d5d4b27fc9ccef7f12968e47c24648f85e295e7c001b4f36f81d6491842318cb52848fd24a8f0f138afe42e3a2136f09d77f9f6c96c361be9413aa693f7504003ea01cb8276e082a62d1bf0f0d83702eb9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "sl=\"delete me\"; Version=1; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-li-uuid": "vZHDyRjuiQCkhZBzyxGqrg==" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "pragma": "no-cache" + }, + { + "expires": "0" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:14:01 GMT" + }, + { + "age": "3" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "x-fs-uuid": "bd91c3c918ee8900a4859073cb11aaae" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"1672258277\"" + }, + { + "last-modified": "Fri, 27 Nov 2009 06:27:21 GMT" + }, + { + "content-length": "6176" + } + ] + }, + { + "seqno": 41, + "wire": "88ca54919d29aee30c78f1e17a0d5752c86a9721e96c96df697e940094d444a820044a01db8db3704ca98b46ff5f8b497ca58e83ee3412c3569fdfd20f0d836801174084f2b124ab04414b414d588ca47e561cc5804f3ad882f3ff6496e4593e940094d444a820059500edc6ddb810a98b46ff6196dc34fd280654d27eea0801128166e05ab801298b46ffd3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Tue, 02 Oct 2012 07:53:23 GMT" + }, + { + "content-type": "text/javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "4012" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=28752189" + }, + { + "expires": "Wed, 02 Oct 2013 07:57:11 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:02 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 42, + "wire": "88d1c46c96d07abe9413ea6a225410022502edc0bb719694c5a37ff7d7e40f0d03363238c2588ba47e561cc581965e0be0776496e4593e940894be522820044a05cb8cbf700fa98b46ffc1d6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Mon, 29 Oct 2012 17:17:34 GMT" + }, + { + "content-type": "image/png" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "628" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=3381907" + }, + { + "expires": "Wed, 12 Dec 2012 16:39:09 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:02 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 43, + "wire": "88d4c7f9d9e60f0d83081a6bc4588ca47e561cc58190b4f81c685f6496dd6d5f4a0195349fba8200595000b8cbd700d298b46fc3d86c96c361be940094d27eea080112817ae32e5c644a62d1bf7f1693cda266caee34789f26cf135fdf93d221861820", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "content-type": "image/png" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-length": "1044" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=31490642" + }, + { + "expires": "Sun, 03 Nov 2013 00:38:04 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:02 GMT" + }, + { + "connection": "keep-alive" + }, + { + "last-modified": "Fri, 02 Nov 2012 18:36:32 GMT" + }, + { + "x-li-uuid": "KMg5e7HswhIQwgDTIysAAA==" + } + ] + }, + { + "seqno": 44, + "wire": "88d8cb6c96e4593e94642a6a2254100225000b8205c03ca62d1bff5f86497ca582211fecdf0f0d840baf89efca588ca47e561cc5819089903adb5f6496df3dbf4a321535112a0801654002e09cb8cb8a62d1bfc9de", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Wed, 31 Oct 2012 00:20:08 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "17928" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=31230754" + }, + { + "expires": "Thu, 31 Oct 2013 00:26:36 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:02 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 45, + "wire": "88dccf6c96df697e94132a6a2254100225020b8105c680a62d1bffc1ef7f0494e7affdbfa9ff6e78db93c4adc9b33de4430c3041e30f0d84085e719fce588ca47e561cc5819036eb4fb4e76496e4593e94132a6a22541002ca8105c0b9704f298b46ffcde2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Tue, 23 Oct 2012 10:10:40 GMT" + }, + { + "content-type": "text/css" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-li-uuid": "YP+9O9z6wRIwf5dQLCsAAA==" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "11863" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=30574946" + }, + { + "expires": "Wed, 23 Oct 2013 10:16:28 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:02 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 46, + "wire": "88e0d36c96d07abe941094d444a820044a05db8105c684a62d1bffd2f3e60f0d8475d0043fd1588ca47e561cc5819036165c79ff6496df697e941094d444a820059502edc0b77190a98b46ffd0e5", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Mon, 22 Oct 2012 17:10:42 GMT" + }, + { + "content-type": "text/javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "77011" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=30513689" + }, + { + "expires": "Tue, 22 Oct 2013 17:15:31 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:02 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 47, + "wire": "887689bf7b3e65a193777b3ff10f0d03333532e96196dc34fd280654d27eea0801128166e05ab806d4c5a37f", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "352" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:14:05 GMT" + } + ] + }, + { + "seqno": 48, + "wire": "88e50f28bf45107f321682a4aa525fe7ed4e25b1063d5007ed4d03f2b43316007da983cd66b0a8837cf6fd2800ad94752c17dd028005c002e040a62d1bfed4d634cf031fe4e3e2f7f3640130e25f87352398ac4c697fe16196dc34fd280654d27eea0801128166e05ab80694c5a37f550131e0ebee5886a8eb10649cbfe0df0f138afe42e3a2136f09d77f9fde0f0d83702eb9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "sl=\"delete me\"; Version=1; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-li-uuid": "vZHDyRjuiQCkhZBzyxGqrg==" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "pragma": "no-cache" + }, + { + "expires": "0" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "image/gif" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:14:04 GMT" + }, + { + "age": "1" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "x-fs-uuid": "bd91c3c918ee8900a4859073cb11aaae" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"1672258277\"" + }, + { + "last-modified": "Fri, 27 Nov 2009 06:27:21 GMT" + }, + { + "content-length": "6176" + } + ] + }, + { + "seqno": 49, + "wire": "886c96c361be940094d27eea080112807ee005719754c5a37f6496c361be9403ea693f75040089403f7002b8cbaa62d1bf5f911d75d0620d263d4c1c88ad6b0a8acf520b409221ea496a4ac9b0752252d8b16a21e435537f858cd50ecf5f0f0d83085e7358a7a47e561cc581b032c845f4a576c74189f4a54759360ea44a7b29fa529b5095ac2f71d0690692ffc84087aaa21ca4498f57842507417f7f3388cc52d6b4341bb97f", + "headers": [ + { + ":status": "200" + }, + { + "last-modified": "Fri, 02 Nov 2012 09:02:37 GMT" + }, + { + "expires": "Fri, 09 Nov 2012 09:02:37 GMT" + }, + { + "content-type": "application/ocsp-response" + }, + { + "content-transfer-encoding": "binary" + }, + { + "content-length": "1186" + }, + { + "cache-control": "max-age=503312, public, no-transform, must-revalidate" + }, + { + "date": "Sat, 03 Nov 2012 13:14:05 GMT" + }, + { + "nncoection": "close" + }, + { + "connection": "Keep-Alive" + } + ] + }, + { + "seqno": 50, + "wire": "887684aa6355e76196dc34fd280654d27eea0801128166e05ab80714c5a37fcaeaf54088ea52d6b0e83772ff8749a929ed4c02076496df3dbf4a002a5f29140befb4a05cb8005c0014c5a37f4085aec1cd48ff86a8eb10649cbfca7f36d2acf4189eac2cb07f33a535dc618f1e3c2e6a6cf07b2893c1a42ae43d2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725f535ee85486fe853570daa64d37d4e1a7229a61e2a5ed5a3f9f", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx" + }, + { + "date": "Sat, 03 Nov 2012 13:14:06 GMT" + }, + { + "content-type": "image/gif" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "keep-alive": "timeout=20" + }, + { + "expires": "Thu, 01 Dec 1994 16:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.imrworldwide.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND UNI NAV COM\"" + } + ] + }, + { + "seqno": 51, + "wire": "887f05842507417fcf58a1aec3771a4bf4a547588324e5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfc16496c361be94034a436cca05f75e5022b8005c0014c5a37f0f0d023335c576025153", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "private, no-cache, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 04 Aug 1978 12:00:00 GMT" + }, + { + "content-length": "35" + }, + { + "date": "Sat, 03 Nov 2012 13:14:06 GMT" + }, + { + "server": "QS" + } + ] + }, + { + "seqno": 52, + "wire": "880f0d0233355a839bd9ab6c96e4593e941054ca3a941000d2817ee361b8c814c5a37f4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb6196df3dbf4a002a693f7504008940b571905c03ea62d1bf6496e4593e940bea435d8a080002810dc699b800298b46ffd77b8b84842d695b05443c86aa6f55850b8f09977f58a9aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52bb0fe7d2d617b8e83483497f7686c58703025c1fcc", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "35" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Wed, 21 Jan 2004 19:51:30 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "date": "Thu, 01 Nov 2012 14:30:09 GMT" + }, + { + "expires": "Wed, 19 Apr 2000 11:43:00 GMT" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "age": "168237" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, proxy-revalidate" + }, + { + "server": "GFE/2.0" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 53, + "wire": "88c30f0d023335c4ccc2c5dbbfc0be", + "headers": [ + { + ":status": "200" + }, + { + "date": "Thu, 01 Nov 2012 14:30:09 GMT" + }, + { + "content-length": "35" + }, + { + "x-content-type-options": "nosniff" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Wed, 19 Apr 2000 11:43:00 GMT" + }, + { + "last-modified": "Wed, 21 Jan 2004 19:51:30 GMT" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, proxy-revalidate" + }, + { + "age": "168237" + }, + { + "server": "GFE/2.0" + } + ] + }, + { + "seqno": 54, + "wire": "89dbc1c66496d07abe940054ca3a940bef814002e001700053168dffd00f0d01307f0c88ea52d6b0e83772ff58b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfcf", + "headers": [ + { + ":status": "204" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:06 GMT" + }, + { + "content-length": "0" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 55, + "wire": "88768c86b19272ad78fe8e92b015c30f288afc5b3e45b25fbd05e0ff7f10f5bdae0fe6f43a94bfbb5a97b56d52f70daa437f4194bf838994df0e4329af7426535eebe65327184ca64e37cca5ed5a4ca6ae1b54bf833994dd0e8329c34ed329af85d329ab7ed329934df535e3e6a6ad39d4e1a7229af86d530e4d2a5ed5a14d30f153269dea5fc1a14bda77a9af567535edc1fcff408bf2b4b60e92ac7ad263d48f89dd0e8c1ab6e4c5934fc76c96e4593e94109486d99410022500fdc6dbb8d094c5a37f5f91497ca589d34d1f649c7620a98386fc2b3d5b842d4b70dd6196dc34fd280654d27eea0801128166e05ab80794c5a37f4087f2b4a85adb4d2797680e0a591a948f3f1b8cb4e14a28dc0bed806fbcd006df7f3093d98b3bfbde347cc11db9858b8deae786bd9041e5798624f6d5d4b27fc9d3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "X-LI-IDC=C1" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Wed, 22 Aug 2012 09:55:42 GMT" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:14:08 GMT" + }, + { + "x-fs-uuid": "4062fd4fc89b6346ee2b61950a9840a5" + }, + { + "x-li-uuid": "QGL9T8ibY0buK2GVCphApQ==" + }, + { + "age": "1" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 56, + "wire": "88c70f28bf45107f321682a4aa525fe7ed4e25b1063d5007ed4d03f2b43316007da983cd66b0a8837cf6fd2800ad94752c17dd028005c002e040a62d1bfed4d634cf031fc67f0094b2fd17b168d3d2bb2c467e1ab4578e7cb93c4107c6cfda6496df3dbf4a002a651d4a05f740a0017000b800298b46ff5886a8eb2127b0bfc6c56196dc34fd280654d27eea0801128166e05ab807d4c5a37f550130c3ced8ea7f06968e47c24648f85e295e7c001b4f36f81d6491842318cb52848fd24a8f0f138afe42e3a2136f09d77f9f6c96c361be9413aa693f7504003ea01cb8276e082a62d1bf0f0d0130", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "sl=\"delete me\"; Version=1; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-li-uuid": "rDlCGMNjprrsLUOMpHhJIw==" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:14:09 GMT" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "x-fs-uuid": "bd91c3c918ee8900a4859073cb11aaae" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"1672258277\"" + }, + { + "last-modified": "Fri, 27 Nov 2009 06:27:21 GMT" + }, + { + "content-length": "0" + } + ] + }, + { + "seqno": 57, + "wire": "88f35f911d75d0620d263d4c795ba0fb8d04b0d5a70f0d03333532dcc3", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "352" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:14:09 GMT" + } + ] + }, + { + "seqno": 58, + "wire": "88d00f288afc5b3e45b25fbd05e0ffcfced7cdf1cbc3c97f0794bd7f5eff4c68e3e3ce6d98735afd3e910c30c107c3c8d3ddeff3e3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "X-LI-IDC=C1" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Wed, 22 Aug 2012 09:55:42 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:14:09 GMT" + }, + { + "x-fs-uuid": "4062fd4fc89b6346ee2b61950a9840a5" + }, + { + "x-li-uuid": "CDPTy/MVwxKQFKu9mysAAA==" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 59, + "wire": "88d154919d29aee30c78f1e17a0d5752c86a9721e96c96df697e940094d444a820044a05db807ee34d298b46fff4da7f0194e7affdbfa9ff6e78db93c4adc9b33de4430c3041e00f0d0236344084f2b124ab04414b414d588ca47e561cc5804f3c165e69df6496df3dbf4a019535112a0801654006e01ab8db8a62d1bfcad9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Tue, 02 Oct 2012 17:09:44 GMT" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-li-uuid": "YP+9O9z6wRIwf5dQLCsAAA==" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "64" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=28813847" + }, + { + "expires": "Thu, 03 Oct 2013 01:04:56 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:09 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 60, + "wire": "88d7c36c96e4593e94642a6a225410022502fdc086e05a53168dff5f87352398ac5754dfe07f0493342ea247eadfe27c9b0b2a18cf7910c30c107fe6588ca47e561cc58190b4e32c85ff6496dc34fd280129a4fdd41002ca8176e00571a794c5a37fcf0f0d03363033de", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Wed, 31 Oct 2012 19:11:14 GMT" + }, + { + "content-type": "image/png" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-li-uuid": "iA7sd9nTwhIQefs/LCsAAA==" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "max-age=31463319" + }, + { + "expires": "Sat, 02 Nov 2013 17:02:48 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:09 GMT" + }, + { + "content-length": "603" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 61, + "wire": "88c10f0d83085b736c96c361be940bca435d8a08007940bd700cdc6da53168df0f13890880f36d05e65a0001768bca54a7d7f4e2e15c4e7f7fc7588ba47e561cc581b0bee34ebb6496e4593e940094ca3a941002ca8172e342b80754c5a37f6196dc34fd280654d27eea0801128166e05ab810298b46ffe3", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "1156" + }, + { + "last-modified": "Fri, 18 Apr 2008 18:03:54 GMT" + }, + { + "etag": "1208541834000" + }, + { + "server": "Jetty(6.1.26)" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=5196477" + }, + { + "expires": "Wed, 02 Jan 2013 16:42:07 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:10 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 62, + "wire": "88c60f0d8365a75c6c96df3dbf4a002a6e2d6a08010a8166e34e5c03ca62d1bf0f138a0b2169e79a75c780007fc2cb588ca47e561cc5804f89a65c71ef6496df697e9403ca6a22541002ca8005c13d719794c5a37fc1e6", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "3476" + }, + { + "last-modified": "Thu, 01 Sep 2011 13:46:08 GMT" + }, + { + "etag": "1314884768000" + }, + { + "server": "Jetty(6.1.26)" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=29243668" + }, + { + "expires": "Tue, 08 Oct 2013 00:28:38 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:10 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 63, + "wire": "88c90f0d830b4e376c96e4593e940b6a5f291410020502fdc6dcb807d4c5a37f0f1389089f134d09f71f0001c5ce588ba47e561cc581b744c804d76496df697e9403ca651d4a08016540bd71b76e36d298b46fc4e9", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "1465" + }, + { + "last-modified": "Wed, 15 Dec 2010 19:56:09 GMT" + }, + { + "etag": "1292442969000" + }, + { + "server": "Jetty(6.1.26)" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=5723024" + }, + { + "expires": "Tue, 08 Jan 2013 18:57:54 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:10 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 64, + "wire": "88cc0f0d830ba06f6c96e4593e940b2a681fa504003ea01cb8d3b700e298b46f0f1389089a105f7442700007c8d1588ca47e561cc5804f3a175d13bf6496df697e940054d444a8200595042b8215c6dd53168dffc7ec", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "1705" + }, + { + "last-modified": "Wed, 13 May 2009 06:47:06 GMT" + }, + { + "etag": "1242197226000" + }, + { + "server": "Jetty(6.1.26)" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=28717727" + }, + { + "expires": "Tue, 01 Oct 2013 22:22:57 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:10 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 65, + "wire": "88cf0f0d830bad3d6c96d07abe940b2a612c6a080112806ae05cb8db4a62d1bf0f13890b227c2071c0b40003cbd4588ca47e561cc5804fb81640c8bf6496dc34fd281129a88950400b2a01db806ae34253168dffcaef", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "1748" + }, + { + "last-modified": "Mon, 13 Feb 2012 04:16:54 GMT" + }, + { + "etag": "1329106614000" + }, + { + "server": "Jetty(6.1.26)" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=29613032" + }, + { + "expires": "Sat, 12 Oct 2013 07:04:42 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:10 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 66, + "wire": "88d20f0d831042df6c96d07abe94038a65b6a50400854086e09eb82794c5a37f0f13890b207596df740f0000ced7588ca47e561cc5804f89b6c0d3df6496df697e9403ca6a22541002ca8066e32f5c0bca62d1bfcdf2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2115" + }, + { + "last-modified": "Mon, 06 Jun 2011 11:28:28 GMT" + }, + { + "etag": "1307359708000" + }, + { + "server": "Jetty(6.1.26)" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=29255048" + }, + { + "expires": "Tue, 08 Oct 2013 03:38:18 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:10 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 67, + "wire": "88d50f0d83101c6f6c96c361be940b6a436cca08007940397196ee32d298b46f0f13890882f3af082cb40003d1588ca47e561cc5804cbef3ed3ad76497df3dbf4a01e521b6650400b2a01ab8dbd71a694c5a37ffd0f5dc", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2065" + }, + { + "last-modified": "Fri, 15 Aug 2008 06:35:34 GMT" + }, + { + "etag": "1218782134000" + }, + { + "server": "Jetty(6.1.26)" + }, + { + "cache-control": "max-age=23989474" + }, + { + "expires": "Thu, 08 Aug 2013 04:58:44 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:10 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-cdn": "AKAM" + } + ] + }, + { + "seqno": 68, + "wire": "88d80f0d830baebb6c96dd6d5f4a01c521aec504003ca05cb8d3571a0298b46f0f13890880eb60009e00000fd4dd588ca47e561cc5804d002db4dbdf6496df3dbf4a01e521b6650400b2a0457021b8d3ca62d1bfd3f8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "1777" + }, + { + "last-modified": "Sun, 06 Apr 2008 16:44:40 GMT" + }, + { + "etag": "1207500280000" + }, + { + "server": "Jetty(6.1.26)" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=24015458" + }, + { + "expires": "Thu, 08 Aug 2013 12:11:48 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:10 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 69, + "wire": "88db0f0d83136cb96c96dd6d5f4a05d521aec50400854086e09cb800298b46ff0f13890b20640cbedb800001d7e0588ca47e561cc581903efb8dba0f6496d07abe9413ca6a22541002ca8076e099b8d014c5a37fd6408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2536" + }, + { + "last-modified": "Sun, 17 Apr 2011 11:26:00 GMT" + }, + { + "etag": "1303039560000" + }, + { + "server": "Jetty(6.1.26)" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=30996570" + }, + { + "expires": "Mon, 28 Oct 2013 07:23:40 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:10 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 70, + "wire": "88df0f0d830b4d376c96dd6d5f4a05a52f948a08007940bf71a76e09e53168df0f13890884f89e680d3c0003dbe4588ca47e561cc58190804cbc273f6496d07abe9413ca6a22541002ca816ae36edc6dc53168dfdac1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "1445" + }, + { + "last-modified": "Sun, 14 Dec 2008 19:47:28 GMT" + }, + { + "etag": "1229284048000" + }, + { + "server": "Jetty(6.1.26)" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=31023826" + }, + { + "expires": "Mon, 28 Oct 2013 14:57:56 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:10 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 71, + "wire": "88e20f0d84085d79bf6c96df697e941054c258d410022500d5c69ab82754c5a37f0f138a0b227dd7df69c740007fdee7588ca47e561cc5804f89c75a75bf6496df697e9403ca6a22541002ca8076e01bb826d4c5a37fddc4", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "11785" + }, + { + "last-modified": "Tue, 21 Feb 2012 04:44:27 GMT" + }, + { + "etag": "1329799467000" + }, + { + "server": "Jetty(6.1.26)" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=29267475" + }, + { + "expires": "Tue, 08 Oct 2013 07:05:25 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:10 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 72, + "wire": "887684aa6355e76196dc34fd280654d27eea0801128166e05ab810a98b46ff5f87352398ac4c697ffac74088ea52d6b0e83772ff8749a929ed4c02076496df3dbf4a002a5f29140befb4a05cb8005c0014c5a37f4085aec1cd48ff86a8eb10649cbf5886a8eb10649cbf4003703370d2acf4189eac2cb07f33a535dc618f1e3c2e6a6cf07b2893c1a42ae43d2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725f535ee85486fe853570daa64d37d4e1a7229a61e2a5ed5a3f9f", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx" + }, + { + "date": "Sat, 03 Nov 2012 13:14:11 GMT" + }, + { + "content-type": "image/gif" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "keep-alive": "timeout=20" + }, + { + "expires": "Thu, 01 Dec 1994 16:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.imrworldwide.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND UNI NAV COM\"" + } + ] + }, + { + "seqno": 73, + "wire": "887f0d842507417fc458a1aec3771a4bf4a547588324e5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfc26496c361be94034a436cca05f75e5022b8005c0014c5a37f0f0d023335c776025153", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "private, no-cache, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 04 Aug 1978 12:00:00 GMT" + }, + { + "content-length": "35" + }, + { + "date": "Sat, 03 Nov 2012 13:14:11 GMT" + }, + { + "server": "QS" + } + ] + }, + { + "seqno": 74, + "wire": "880f0d0233355a839bd9ab6c96e4593e941054ca3a941000d2817ee361b8c814c5a37f4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb6196df3dbf4a002a693f7504008940b571905c03ea62d1bf6496e4593e940bea435d8a080002810dc699b800298b46ffcc7b8b84842d695b05443c86aa6f55850b8f09a17f58a9aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52bb0fe7d2d617b8e83483497f7686c58703025c1fcd", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "35" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Wed, 21 Jan 2004 19:51:30 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "date": "Thu, 01 Nov 2012 14:30:09 GMT" + }, + { + "expires": "Wed, 19 Apr 2000 11:43:00 GMT" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "age": "168242" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, proxy-revalidate" + }, + { + "server": "GFE/2.0" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 75, + "wire": "88c30f0d023335c4cdc2c5d0bfc0be", + "headers": [ + { + ":status": "200" + }, + { + "date": "Thu, 01 Nov 2012 14:30:09 GMT" + }, + { + "content-length": "35" + }, + { + "x-content-type-options": "nosniff" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Wed, 19 Apr 2000 11:43:00 GMT" + }, + { + "last-modified": "Wed, 21 Jan 2004 19:51:30 GMT" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, proxy-revalidate" + }, + { + "age": "168242" + }, + { + "server": "GFE/2.0" + } + ] + }, + { + "seqno": 76, + "wire": "89d0c1c66496d07abe940054ca3a940bef814002e001700053168dffd20f0d0130da58b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfcf", + "headers": [ + { + ":status": "204" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:11 GMT" + }, + { + "content-length": "0" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 77, + "wire": "885f88352398ac74acb37f0f0d830802cf6c96dc34fd280654d27eea080112806ee34d5c65c53168df0f138a0b2d85f105a75c69e6ff768c86b19272ad78fe8e92b015c34084f2b124ab04414b414d588ca47e561cc58190b6079f65bf6497dd6d5f4a0195349fba820059500ddc699b80714c5a37ffd9e14087f2b5065adb4d2793b6ecd8cd4f77fc4f93c1edd76e6f4886186083cfca", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "1013" + }, + { + "last-modified": "Sat, 03 Nov 2012 05:44:36 GMT" + }, + { + "etag": "1351921476485" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=31508935" + }, + { + "expires": "Sun, 03 Nov 2013 05:43:06 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:11 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-li-uuid": "uBgHimv9whIwouPuKysAAA==" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 78, + "wire": "88c20f288afc5b3e45b25fbd05e0ff7f15f5bdae0fe6f43a94bfbb5a97b56d52f70daa437f4194bf838994df0e4329af7426535eebe65327184ca64e37cca5ed5a4ca6ae1b54bf833994dd0e8329c34ed329af85d329ab7ed329934df535e3e6a6ad39d4e1a7229af86d530e4d2a5ed5a14d30f153269dea5fc1a14bda77a9af567535edc1fcff408bf2b4b60e92ac7ad263d48f89dd0e8c1ab6e4c5934fcc6c96df3dbf4a01f521b665040089400ae340b82794c5a37f5f91497ca589d34d1f649c7620a98386fc2b3d5b842d4b70dd6196dc34fd280654d27eea0801128166e05ab811298b46ff4087f2b4a85adb4d27978dd6647e424b2b447a4680071d69c78b2b8f3e503637d97f06934fb149ed8df9030ddab69dd118b747d7c4107f550131798624f6d5d4b27fecd9df640130e10f0d8371b03b", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "X-LI-IDC=C1" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Thu, 09 Aug 2012 02:40:28 GMT" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:14:12 GMT" + }, + { + "x-fs-uuid": "b73d9dcff4c8d40067468ef689e05a93" + }, + { + "x-li-uuid": "tz2dz/TI1ABnRo72ieBakw==" + }, + { + "age": "1" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "6507" + } + ] + }, + { + "seqno": 79, + "wire": "88cd0f28bf45107f321682a4aa525fe7ed4e25b1063d5007ed4d03f2b43316007da983cd66b0a8837cf6fd2800ad94752c17dd028005c002e040a62d1bfed4d634cf031fc87f0293ff6f6af55b26092872b134cf2e2ce7d0e4d041c8d6e26496df3dbf4a002a651d4a05f740a0017000b800298b46ff5886a8eb2127b0bfc8c76196dc34fd280654d27eea0801128166e05ab81654c5a37f550130c4f2dfe57f08968e47c24648f85e295e7c001b4f36f81d6491842318cb52848fd24a8f0f138afe42e3a2136f09d77f9f6c96c361be9413aa693f7504003ea01cb8276e082a62d1bf0f0d0130", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "sl=\"delete me\"; Version=1; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-li-uuid": "+8Oyp3i1cl6p243WV3LM6g==" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:14:13 GMT" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "x-fs-uuid": "bd91c3c918ee8900a4859073cb11aaae" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"1672258277\"" + }, + { + "last-modified": "Fri, 27 Nov 2009 06:27:21 GMT" + }, + { + "content-length": "0" + } + ] + }, + { + "seqno": 80, + "wire": "887689bf7b3e65a193777b3f5f911d75d0620d263d4c795ba0fb8d04b0d5a70f0d03353337e4c4", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "537" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:14:13 GMT" + } + ] + }, + { + "seqno": 81, + "wire": "88bf5f87497ca589d34d1f0f0d83134e3fe5c5", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "text/html" + }, + { + "content-length": "2469" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:14:13 GMT" + } + ] + }, + { + "seqno": 82, + "wire": "88e05f8b497ca58e83ee3412c3569f6c96df697e9403ca681fa50400894102e01fb80754c5a37f6196c361be940094d27eea080112816ae320b807d4c5a37f6496dc34fd280654d27eea080112816ae320b807d4c5a37fe7e9768344b2970f0d826441408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f5584782f34df5890aed8e8313e94a47e561cc581e71a003f", + "headers": [ + { + ":status": "200" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "text/javascript" + }, + { + "last-modified": "Tue, 08 May 2012 20:09:07 GMT" + }, + { + "date": "Fri, 02 Nov 2012 14:30:09 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 14:30:09 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "server": "sffe" + }, + { + "content-length": "321" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "81845" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 83, + "wire": "88e8e26c96df697e941054c258d4100225001b8066e34fa98b46ff6196c361be940094d27eea0801128205c033704fa98b46ff6496dc34fd280654d27eea0801128205c033704fa98b46ffeef0c40f0d8413416dcfc35584702f34dfc2", + "headers": [ + { + ":status": "200" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Tue, 21 Feb 2012 01:03:49 GMT" + }, + { + "date": "Fri, 02 Nov 2012 20:03:29 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 20:03:29 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "server": "sffe" + }, + { + "content-length": "24156" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "61845" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 84, + "wire": "88e454919d29aee30c78f1e17a0d5752c86a9721e96c96c361be940094d27eea080112817ae0417196d4c5a37f5f87352398ac5754dfeff4588ca47e561cc58190b626dd783f6496dd6d5f4a0195349fba8200595020b8266e36d298b46fd60f0d0239347f3b88ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Fri, 02 Nov 2012 18:10:35 GMT" + }, + { + "content-type": "image/png" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "max-age=31525781" + }, + { + "expires": "Sun, 03 Nov 2013 10:23:54 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:13 GMT" + }, + { + "content-length": "94" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 85, + "wire": "88768586b19272ff0f13a2fe5b95d211f03c020e4009965969a6d971d906571a6da724b8165b0800e34e39fcff6c96df697e94132a6a225410022502ddc65ab82714c5a37fd6d3f4f95889a47e561cc58197000f6196dc34fd280654d27eea0801128166e05ab81694c5a37f0f0d830804f7c2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "etag": "\"5f7cc9080cad02333445367dae64546d:1351006466\"" + }, + { + "last-modified": "Tue, 23 Oct 2012 15:34:26 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "max-age=3600" + }, + { + "date": "Sat, 03 Nov 2012 13:14:14 GMT" + }, + { + "content-length": "1028" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 86, + "wire": "88d5fb6c96d07abe940bca65b6a504008940b37022b8dbaa62d1bfd90f138efe4b1b8c902d36d3521240dc07f3f7768dd06258741e54ad9326e61d5c1f4089f2b567f05b0b22d1fa868776b5f4e0df0f0d8313edb7c1c5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Mon, 18 Jun 2012 13:12:57 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"eb63c14544dcd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/7.0" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "2955" + }, + { + "date": "Sat, 03 Nov 2012 13:14:14 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 87, + "wire": "88c1c46c96dc34fd280654d27eea0801128172e09bb8d3ea62d1bf6496e4593e9403aa693f7504008940b9704ddc69f53168df0f139f69c7dd75a035840d3acb8fb976edfbcd85eba179a6ef380c0f01f859134fff58a5a47e561cc58196dc69f6beabb63a0c4faa8eb26c1d4894f653f54da84ad617b8e83483497f408df2b1c88ad6b0b59ea90b62c693884bc5908339115b9f0f0d033437317f0a842507417f5f911d75d0620d263d4c1c88ad6b0a8acf520b", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:14:14 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Sat, 03 Nov 2012 16:25:49 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 16:25:49 GMT" + }, + { + "etag": "46977404F0473696BBDC518B1845C60E809A3249" + }, + { + "cache-control": "max-age=356494,public,no-transform,must-revalidate" + }, + { + "x-ocsp-reponder-id": "t8edcaocsp6" + }, + { + "content-length": "471" + }, + { + "connection": "close" + }, + { + "content-type": "application/ocsp-response" + } + ] + }, + { + "seqno": 88, + "wire": "88ca0f13a1fe5c6dd79c209f08da700c8c6d85b00c2f3cd34d89e65e92e044e8596c226dafe76c96df3dbf4a05b521aec504008140bb700edc13ea62d1bfe25f87352398ac4c697f7b8b84842d695b05443c86aa6f5a839bd9ab588da47e561cc5804169a69a780007cc0f0d023433d0", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "etag": "\"65786c291a4603aa5150a1884452838d:1271351254\"" + }, + { + "last-modified": "Thu, 15 Apr 2010 17:07:29 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "max-age=2144448000" + }, + { + "date": "Sat, 03 Nov 2012 13:14:14 GMT" + }, + { + "content-length": "43" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 89, + "wire": "88cf0f13a2fe5a249234ec6ec7205b95d6de65e6996e50880e8c81682d5c0b2d840071f7d9fe7f6c96df697e94132a6a225410022502ddc699b81654c5a37fe7e4c1c0588ca47e561cc58190b6cb800001ce0f0d840884c8bfd2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "etag": "\"4cdd47b7bd15f75838435f1207ac1414:1351006993\"" + }, + { + "last-modified": "Tue, 23 Oct 2012 15:43:13 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "max-age=315360000" + }, + { + "date": "Sat, 03 Nov 2012 13:14:14 GMT" + }, + { + "content-length": "12232" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 90, + "wire": "48826402768c86b19272ad78fe8e92b015c37f3c9fbdae0fe6f6ad0a69878a9934ef5376f854d392fa9ab86d53269bea69d593f94085aec1cd48ff86a8eb10649cbf5886a8eb10649cbf0f28bf213afb803493c7a66cfb52f9e919aa8292c861b92166b0a542e43d3f6a60f359ac2a20df3dbf4a004b681fa58400b2a059b816ae05b53168dff6a6b1a678180f1fc49d29aee30c0c8931ea5e92c861b92166b0a542e43d2c1ec8d05b3bb1523a23fca89de0659f8a91009f7fe2b2778197fe2a2401f8acde7249005903ce7c109d7dc09b2d2f0f0d0130d3cb", + "headers": [ + { + ":status": "302" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "CP=\"COM NAV INT STA NID OUR IND NOI\"" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "set-cookie": "cckz=1mcwy3r; Domain=media6degrees.com; Expires=Thu, 02-May-2013 13:14:15 GMT; Path=/" + }, + { + "location": "http://action.media6degrees.com/orbserv/nsjs?ncv=33&ns=299&pcv=39&nc=1&pixId=13086&cckz=true" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:14:14 GMT" + }, + { + "connection": "close" + } + ] + }, + { + "seqno": 91, + "wire": "88c1bef4bfc8c76196dc34fd280654d27eea0801128166e05ab816d4c5a37ff0f6d8c7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "date": "Sat, 03 Nov 2012 13:14:15 GMT" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 92, + "wire": "88c20f288afc5b3e45b25fbd05e0ff7f02f5bdae0fe6f43a94bfbb5a97b56d52f70daa437f4194bf838994df0e4329af7426535eebe65327184ca64e37cca5ed5a4ca6ae1b54bf833994dd0e8329c34ed329af85d329ab7ed329934df535e3e6a6ad39d4e1a7229af86d530e4d2a5ed5a14d30f153269dea5fc1a14bda77a9af567535edc1fcff408bf2b4b60e92ac7ad263d48f89dd0e8c1ab6e4c5934fca6c96df697e940baa65b68504008941337020b8d36a62d1bf5f91497ca589d34d1f649c7620a98386fc2b3d5b842d4b70ddc37f359708196a3046e11d7640caf85e0be3148f89f75c6a36de177f3b93c17f7784a247f1b78aed2616ea05b8334d041ff7fddfcec6fcc70f0d8371b03b", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "X-LI-IDC=C1" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Tue, 17 Jul 2012 23:10:45 GMT" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:14:15 GMT" + }, + { + "x-fs-uuid": "1034b0b6c77d1f91819a2d929764b582" + }, + { + "x-li-uuid": "EDSwtsd9H5GBmi2Sl2S1gg==" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "6507" + } + ] + }, + { + "seqno": 93, + "wire": "88c90f28bf45107f321682a4aa525fe7ed4e25b1063d5007ed4d03f2b43316007da983cd66b0a8837cf6fd2800ad94752c17dd028005c002e040a62d1bfed4d634cf031fc47e9336bdd77d73dfc44d05d16fd186126b26e9a083c4d0c8fbfac2c1c6f8fee0cfc7f7f60f138afe42e3a2136f09d77f9ff50f0d0130", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "sl=\"delete me\"; Version=1; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-li-uuid": "iPSByYTV24172TMFAcPcSg==" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:14:15 GMT" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "x-fs-uuid": "bd91c3c918ee8900a4859073cb11aaae" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"1672258277\"" + }, + { + "last-modified": "Fri, 27 Nov 2009 06:27:21 GMT" + }, + { + "content-length": "0" + } + ] + }, + { + "seqno": 94, + "wire": "88f4f30f0d03353339cf6196dc34fd280654d27eea0801128166e05ab81714c5a37f", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "539" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:14:16 GMT" + } + ] + }, + { + "seqno": 95, + "wire": "cccbcac9c80f28bf213afb803493c7a651f6a5f3d23355052590c37242cd614a85c87a7ed4c1e6b3585441be7b7e940096d03f4b08016540b3702d5c0b8a62d1bfed4d634cf0310f1fc49d29aee30c0c8931ea5e92c861b92166b0a542e43d2c1ec8d05b3bb1523a23fca89de0659f8a91009f7fe2b2778197fe2a2401f8acde7249005903ce7c109d7dc09b2d2f0f0d0130bed5", + "headers": [ + { + ":status": "302" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "CP=\"COM NAV INT STA NID OUR IND NOI\"" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "set-cookie": "cckz=1mcwy3s; Domain=media6degrees.com; Expires=Thu, 02-May-2013 13:14:16 GMT; Path=/" + }, + { + "location": "http://action.media6degrees.com/orbserv/nsjs?ncv=33&ns=299&pcv=39&nc=1&pixId=13086&cckz=true" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:14:16 GMT" + }, + { + "connection": "close" + } + ] + }, + { + "seqno": 96, + "wire": "88cbcac9c80f28b6cbbb06edd93569c97e0bd81966fe1c0edf7da75d7ef059ba06af357dfbad337dd75f17da9ac699e060f64682d9dfed4c694d7aaaa3d75f95497ca589d34d1f649c7620a98326ed4b3cf36fac1fc30f0d0135c8d6", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "CP=\"COM NAV INT STA NID OUR IND NOI\"" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "set-cookie": "JSESSIONID=CE33DFE7D94779C13B04C4D9B43D7792; Path=/orbserv; HttpOnly" + }, + { + "content-type": "text/html;charset=ISO-8859-1" + }, + { + "content-language": "en-US" + }, + { + "content-length": "5" + }, + { + "date": "Sat, 03 Nov 2012 13:14:15 GMT" + }, + { + "connection": "close" + } + ] + }, + { + "seqno": 97, + "wire": "88f6f40f0d03383431d1bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "text/html" + }, + { + "content-length": "841" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:14:16 GMT" + } + ] + }, + { + "seqno": 98, + "wire": "88cc0f28ff0db607bfe02ec3373caff03e2bd2f1cd0c30c30c30c3b30430ecc93c83f10c3b667e5da4661861861877bfafbcd0c30c30c36bffb2cd0c30c30ea8b8a785ebba2ec30db773ec87ed4e25b1063d5007ed4be7a466aa05c7375a9721e9fb5340fcad0cc581c640e880007da983cd66b0a88341eafa500cada4fdd61002d28166e05ab81714c5a37fda9ac699e0637f08b6bdae0fe74eac8a5fddad4bdab6a97b86d1a90dfd0352fe0e23537c3906a6ae1b54bbc3729934df53869c8a5ed5a14d30f153269dffcf6495dc34fd2800a994752820000a0017000b800298b46fcc5892a8eb10649cbf4a536a12b585ee3a0d20d25f5f95352398ac4c697ec938ec4153064dda9679e6df583fc70f0d023433ccda", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "u=8|0BAgYJ9UoGCfVKAAAAAAAAQEAAQIhdawAARg9fRc3AAAAAAT9PvgAAAAAAu9ZfgAAAAAO_VtUCBMBAAuBLQA; Version=1; Domain=.agkn.com; Max-Age=63072000; Expires=Mon, 03-Nov-2014 13:14:16 GMT; Path=/" + }, + { + "p3p": "CP=\"NOI DSP COR CURa ADMa DEVa TAIa OUR BUS IND UNI COM NAV INT\"" + }, + { + "expires": "Sat, 01 Jan 2000 00:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "image/gif;charset=ISO-8859-1" + }, + { + "content-language": "en-US" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:14:15 GMT" + }, + { + "connection": "close" + } + ] + }, + { + "seqno": 99, + "wire": "88d00f288afc5b3e45b25fbd05e0ffcbcad6c9d7c7c3c6c5550130798624f6d5d4b27fe8d7cf640130d10f0d8371b03b", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "X-LI-IDC=C1" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Tue, 17 Jul 2012 23:10:45 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:14:16 GMT" + }, + { + "x-fs-uuid": "1034b0b6c77d1f91819a2d929764b582" + }, + { + "x-li-uuid": "EDSwtsd9H5GBmi2Sl2S1gg==" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "6507" + } + ] + }, + { + "seqno": 100, + "wire": "88ec0f0d831019736c96df3dbf4a09f5349fba82001d502fdc03f702053168df0f1389085f71971965b00000768bca54a7d7f4e2e15c4e7f7f4084f2b124ab04414b414d588ca47e561cc58190800dbafbbf6496d07abe9413ca6a22541002ca807ee36edc65953168dfcbee7f0d93b6ecd8cd4f77fc4f93c1edd76e6f4886186083dedf", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "2036" + }, + { + "last-modified": "Thu, 29 Nov 2007 19:09:10 GMT" + }, + { + "etag": "1196363350000" + }, + { + "server": "Jetty(6.1.26)" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=31005797" + }, + { + "expires": "Mon, 28 Oct 2013 09:57:33 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:16 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-li-uuid": "uBgHimv9whIwouPuKysAAA==" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 101, + "wire": "88d9f46c96dc34fd2816d4dc5ad410022500e5c10ae32da98b46fff3e0df588ca47e561cc5804eb2d36eb2f76496d07abe940b8a6e2d6a0801654006e05cb8cb4a62d1bfcf0f0d03313436f2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "access-control-allow-origin": "http://www.linkedin.com" + }, + { + "last-modified": "Sat, 15 Sep 2012 06:22:35 GMT" + }, + { + "content-type": "image/png" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "max-age=27345738" + }, + { + "expires": "Mon, 16 Sep 2013 01:16:34 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:16 GMT" + }, + { + "content-length": "146" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 102, + "wire": "88e25f88352398ac74acb37f6c96df697e94032a65b6850400894106e05bb8c854c5a37f6196dc34fd280654d27eea0801128066e320b80754c5a37f6496dd6d5f4a01a5349fba820044a019b8c82e01d53168df4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cbe6768344b2970f0d840beebcef408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f558465b034ff5890aed8e8313e94a47e561cc581e71a003f", + "headers": [ + { + ":status": "200" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-type": "image/jpeg" + }, + { + "last-modified": "Tue, 03 Jul 2012 21:15:31 GMT" + }, + { + "date": "Sat, 03 Nov 2012 03:30:07 GMT" + }, + { + "expires": "Sun, 04 Nov 2012 03:30:07 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-encoding": "gzip" + }, + { + "server": "sffe" + }, + { + "content-length": "19787" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "35049" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 103, + "wire": "8b5f911d75d0620d263d4c795ba0fb8d04b0d5a7ebf752848fd24a8f0f138efe4b1b8c902d36d3521240dc07f3edf7f60f0d8313edb7dafd", + "headers": [ + { + ":status": "304" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Mon, 18 Jun 2012 13:12:57 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"eb63c14544dcd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/7.0" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "2955" + }, + { + "date": "Sat, 03 Nov 2012 13:14:16 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 104, + "wire": "88fc0f13a3fe5e699644575b6dc71a75b69991b95e75c69d959746f0dc92e05969c03cebee39fcff6c96d07abe9413aa436cca0801128176e05fb82714c5a37fbfc0eeed5888a47e561cc581c003dc0f0d033734317f3488ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "etag": "\"84332e7556647543d5f87647f37a8a6d:1346087966\"" + }, + { + "last-modified": "Mon, 27 Aug 2012 17:19:26 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-type": "application/x-javascript" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "max-age=600" + }, + { + "date": "Sat, 03 Nov 2012 13:14:16 GMT" + }, + { + "content-length": "741" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 105, + "wire": "887684aa6355e7def2d7bf4088ea52d6b0e83772ff8749a929ed4c02076496df3dbf4a002a5f29140befb4a05cb8005c0014c5a37febea7f1fd2acf4189eac2cb07f33a535dc618f1e3c2e6a6cf07b2893c1a42ae43d2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725f535ee85486fe853570daa64d37d4e1a7229a61e2a5ed5a3f9f", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx" + }, + { + "date": "Sat, 03 Nov 2012 13:14:16 GMT" + }, + { + "content-type": "image/gif" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "keep-alive": "timeout=20" + }, + { + "expires": "Thu, 01 Dec 1994 16:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.imrworldwide.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND UNI NAV COM\"" + } + ] + }, + { + "seqno": 106, + "wire": "88f8f558a1aec3771a4bf4a547588324e5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfed6496c361be94034a436cca05f75e5022b8005c0014c5a37f0f0d023335e376025153", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "private, no-cache, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 04 Aug 1978 12:00:00 GMT" + }, + { + "content-length": "35" + }, + { + "date": "Sat, 03 Nov 2012 13:14:16 GMT" + }, + { + "server": "QS" + } + ] + }, + { + "seqno": 107, + "wire": "880f0d023335f66c96e4593e941054ca3a941000d2817ee361b8c814c5a37fcf6196df3dbf4a002a693f7504008940b571905c03ea62d1bf6496e4593e940bea435d8a080002810dc699b800298b46fffbfa55850b8f09a77f58a9aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52bb0fe7d2d617b8e83483497f7686c58703025c1ff5", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "35" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Wed, 21 Jan 2004 19:51:30 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "date": "Thu, 01 Nov 2012 14:30:09 GMT" + }, + { + "expires": "Wed, 19 Apr 2000 11:43:00 GMT" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "age": "168247" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, proxy-revalidate" + }, + { + "server": "GFE/2.0" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 108, + "wire": "88c20f0d023335d4f5c1c35f87352398ac4c697fc0c1bf", + "headers": [ + { + ":status": "200" + }, + { + "date": "Thu, 01 Nov 2012 14:30:09 GMT" + }, + { + "content-length": "35" + }, + { + "x-content-type-options": "nosniff" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Wed, 19 Apr 2000 11:43:00 GMT" + }, + { + "last-modified": "Wed, 21 Jan 2004 19:51:30 GMT" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, proxy-revalidate" + }, + { + "age": "168247" + }, + { + "server": "GFE/2.0" + } + ] + }, + { + "seqno": 109, + "wire": "88768586b19272ff0f13a2fe650b2eb4e32eb528c2d86313438e47a395f7c6cbee3cebd702cb4e89f699699fe76c96d07abe940814dc5ad410022502e5c13771a654c5a37fd15f87352398ac5754df7b8b84842d695b05443c86aa6f5a839bd9ab588da47e561cc5804169a69a780007f10f0d830804f7d2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "etag": "\"f13746374fa151b24abd8bf99a396878:1347294343\"" + }, + { + "last-modified": "Mon, 10 Sep 2012 16:25:43 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-type": "image/png" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "max-age=2144448000" + }, + { + "date": "Sat, 03 Nov 2012 13:14:16 GMT" + }, + { + "content-length": "1028" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 110, + "wire": "88c30f13a1fe5a91b28e464928c619647dc138c85f742e8084196403b702cb4e89f699785fcf6c96d07abe940814dc5ad410022502e5c139704253168dffd60f0d830b6077c2bff2d3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "etag": "\"4d5ead3cfaa1fd96263197170ccaed07:1347294382\"" + }, + { + "last-modified": "Mon, 10 Sep 2012 16:26:22 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "1507" + }, + { + "content-type": "image/png" + }, + { + "cache-control": "max-age=2144448000" + }, + { + "date": "Sat, 03 Nov 2012 13:14:16 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 111, + "wire": "89d3", + "headers": [ + { + ":status": "204" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 112, + "wire": "89c5c1c06496d07abe940054ca3a940bef814002e001700053168dfff30f0d0130d458b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bf4085aec1cd48ff86a8eb10649cbf", + "headers": [ + { + ":status": "204" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:16 GMT" + }, + { + "content-length": "0" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 113, + "wire": "88768c86b19272ad78fe8e92b015c30f288afc5b3e45b25fbd05e0ff7f14f5bdae0fe6f43a94bfbb5a97b56d52f70daa437f4194bf838994df0e4329af7426535eebe65327184ca64e37cca5ed5a4ca6ae1b54bf833994dd0e8329c34ed329af85d329ab7ed329934df535e3e6a6ad39d4e1a7229af86d530e4d2a5ed5a14d30f153269dea5fc1a14bda77a9af567535edc1fcff408bf2b4b60e92ac7ad263d48f89dd0e8c1ab6e4c5934fc76c96dd6d5f4a01b521b66504008940b77196ee34ca98b46f5f91497ca589d34d1f649c7620a98386fc2b3d5b842d4b70dd6196dc34fd280654d27eea0801128166e05ab81794c5a37f4087f2b4a85adb4d27972b521231c92cad3adb4069e6c658e36e403af8dc704d8b7f30936f4fa719636876609c6e3bf9b0a3fd3709a083f8f7dfcc5886a8eb10649cbff7c80f0d8371b03b", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "X-LI-IDC=C1" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Sun, 05 Aug 2012 15:35:43 GMT" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:14:18 GMT" + }, + { + "x-fs-uuid": "e4dcbadff47540485aebb5d079a66252" + }, + { + "x-li-uuid": "5Ny63/R1QEha67XQeaZiUg==" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "6507" + } + ] + }, + { + "seqno": 114, + "wire": "88c70f28bf45107f321682a4aa525fe7ed4e25b1063d5007ed4d03f2b43316007da983cd66b0a8837cf6fd2800ad94752c17dd028005c002e040a62d1bfed4d634cf031fc67f0094ede0dfee771cf17b9f2f7bb493317cf16bd78820c6cfc96496df3dbf4a002a651d4a05f740a0017000b800298b46ff5886a8eb2127b0bfc6c5c4550131fce4d1c27f05968e47c24648f85e295e7c001b4f36f81d6491842318cbe80f138afe42e3a2136f09d77f9f6c96c361be9413aa693f7504003ea01cb8276e082a62d1bf0f0d0130", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "sl=\"delete me\"; Version=1; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-li-uuid": "qwi+h66wCYWzSNcKexV4yw==" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:14:18 GMT" + }, + { + "age": "1" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "x-fs-uuid": "bd91c3c918ee8900a4859073cb11aaae" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"1672258277\"" + }, + { + "last-modified": "Fri, 27 Nov 2009 06:27:21 GMT" + }, + { + "content-length": "0" + } + ] + }, + { + "seqno": 115, + "wire": "887689bf7b3e65a193777b3feb0f0d03353338d46196dc34fd280654d27eea0801128166e05ab817d4c5a37f", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "538" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:14:19 GMT" + } + ] + }, + { + "seqno": 116, + "wire": "88cf7f0f9fbdae0fe6f6ad0a69878a9934ef5376f854d392fa9ab86d53269bea69d593f9d1c70f28b6cbbb06edd93569c97e086171fbf85e7997aeb2cb8cb975e730b30df6df0bb7c4f5fbff6a6b1a678183d91a0b677fb531a535eaaa8f5f5f95497ca589d34d1f649c7620a98326ed4b3cf36fac1fcc0f0d0135c07f2b842507417f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "CP=\"COM NAV INT STA NID OUR IND NOI\"" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "set-cookie": "JSESSIONID=AA69DF8838B33636B86F3AD5917D28DD; Path=/orbserv; HttpOnly" + }, + { + "content-type": "text/html;charset=ISO-8859-1" + }, + { + "content-language": "en-US" + }, + { + "content-length": "5" + }, + { + "date": "Sat, 03 Nov 2012 13:14:19 GMT" + }, + { + "connection": "close" + } + ] + }, + { + "seqno": 117, + "wire": "88c25f87497ca589d34d1f0f0d03383539d9c2", + "headers": [ + { + ":status": "200" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "text/html" + }, + { + "content-length": "859" + }, + { + "content-encoding": "gzip" + }, + { + "date": "Sat, 03 Nov 2012 13:14:19 GMT" + } + ] + }, + { + "seqno": 118, + "wire": "88d30f288afc5b3e45b25fbd05e0ffd2d1dad0decec2cc7f0a941e2ef818efc38f8f39bc68a326dcb7910c30c107550130798624f6d5d4b27fefdccd640130d80f0d8371b03b", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "X-LI-IDC=C1" + }, + { + "p3p": "CP=\"CAO DSP COR CUR ADMi DEVi TAIi PSAi PSDi IVAi IVDi CONi OUR DELi SAMi UNRi PUBi OTRi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT POL PRE\"" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "vary": "Accept-Encoding" + }, + { + "last-modified": "Sun, 05 Aug 2012 15:35:43 GMT" + }, + { + "content-type": "image/gif" + }, + { + "content-language": "en-US" + }, + { + "date": "Sat, 03 Nov 2012 13:14:19 GMT" + }, + { + "x-fs-uuid": "e4dcbadff47540485aebb5d079a66252" + }, + { + "x-li-uuid": "aGvE/vUVwxKwMlIRJCsAAA==" + }, + { + "age": "0" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "no-cache" + }, + { + "expires": "0" + }, + { + "pragma": "no-cache" + }, + { + "content-length": "6507" + } + ] + }, + { + "seqno": 119, + "wire": "88d70f28ff0db607bfe02ec3373caff03e2bd2f1cde21861861bb0ecc10c3b364f20fc430ed99f976919861861861defebef3430c30c30dadf2b6186186187545c53c2f5dd176186dbb9f643f6a712d8831ea803f6a5f3d2335502e39bad4b90f4fda9a07e568662c0e3207440003ed4c1e6b3585441a0f57d280656d27eeb08016940b3702d5c0bea62d1bfed4d634cf0317f06b6bdae0fe74eac8a5fddad4bdab6a97b86d1a90dfd0352fe0e23537c3906a6ae1b54bbc3729934df53869c8a5ed5a14d30f153269dffcf6495dc34fd2800a994752820000a0017000b800298b46fda5892a8eb10649cbf4a536a12b585ee3a0d20d25f5f95352398ac4c697ec938ec4153064dda9679e6df583fd60f0d023433d5c7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "u=8|0BAgYJ9UoGCfVKwAAAAABAQEAAQQhdawAARg9fRc3AAAAAAT9PvgAAAAAAu5WuAAAAAAO_VtUCBMBAAuBLQA; Version=1; Domain=.agkn.com; Max-Age=63072000; Expires=Mon, 03-Nov-2014 13:14:19 GMT; Path=/" + }, + { + "p3p": "CP=\"NOI DSP COR CURa ADMa DEVa TAIa OUR BUS IND UNI COM NAV INT\"" + }, + { + "expires": "Sat, 01 Jan 2000 00:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "image/gif;charset=ISO-8859-1" + }, + { + "content-language": "en-US" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:14:18 GMT" + }, + { + "connection": "close" + } + ] + }, + { + "seqno": 120, + "wire": "88e30f0d83642f336c96df697e94034a681d8a08007940b971b66e05d53168df0f13890880d38d3edbee8000768bca54a7d7f4e2e15c4e7f7f4084f2b124ab04414b414d588ca47e561cc5819085f7d969df6496e4593e94640a6a22541002ca816ee34cdc138a62d1bfcff97f0b93b6ecd8cd4f77fc4f93c1edd76e6f4886186083e7e8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/png" + }, + { + "content-length": "3183" + }, + { + "last-modified": "Tue, 04 Mar 2008 16:53:17 GMT" + }, + { + "etag": "1204649597000" + }, + { + "server": "Jetty(6.1.26)" + }, + { + "x-cdn": "AKAM" + }, + { + "cache-control": "max-age=31199347" + }, + { + "expires": "Wed, 30 Oct 2013 15:43:26 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:19 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-li-uuid": "uBgHimv9whIwouPuKysAAA==" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 121, + "wire": "8b5f911d75d0620d263d4c795ba0fb8d04b0d5a7e86c96d07abe940bca65b6a504008940b37022b8dbaa62d1bf52848fd24a8f0f138efe4b1b8c902d36d3521240dc07f3eb768dd06258741e54ad9326e61d5c1f4089f2b567f05b0b22d1fa868776b5f4e0df0f0d8313edb7d57f1388ea52d6b0e83772ff", + "headers": [ + { + ":status": "304" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Mon, 18 Jun 2012 13:12:57 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"eb63c14544dcd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/7.0" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "2955" + }, + { + "date": "Sat, 03 Nov 2012 13:14:19 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 122, + "wire": "89be", + "headers": [ + { + ":status": "204" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 123, + "wire": "887684aa6355e7d7f3d0bf4088ea52d6b0e83772ff8749a929ed4c02076496df3dbf4a002a5f29140befb4a05cb8005c0014c5a37febe17f11d2acf4189eac2cb07f33a535dc618f1e3c2e6a6cf07b2893c1a42ae43d2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a9a725f535ee85486fe853570daa64d37d4e1a7229a61e2a5ed5a3f9f", + "headers": [ + { + ":status": "200" + }, + { + "server": "nginx" + }, + { + "date": "Sat, 03 Nov 2012 13:14:19 GMT" + }, + { + "content-type": "image/gif" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + }, + { + "keep-alive": "timeout=20" + }, + { + "expires": "Thu, 01 Dec 1994 16:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "policyref=\"http://www.imrworldwide.com/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA ADM OUR IND UNI NAV COM\"" + } + ] + }, + { + "seqno": 124, + "wire": "88d7f658a1aec3771a4bf4a547588324e5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfed6496c361be94034a436cca05f75e5022b8005c0014c5a37f0f0d023335dc76025153", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "private, no-cache, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 04 Aug 1978 12:00:00 GMT" + }, + { + "content-length": "35" + }, + { + "date": "Sat, 03 Nov 2012 13:14:19 GMT" + }, + { + "server": "QS" + } + ] + }, + { + "seqno": 125, + "wire": "880f0d023335f46c96e4593e941054ca3a941000d2817ee361b8c814c5a37f4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb6196df3dbf4a002a693f7504008940b571905c03ea62d1bf6496e4593e940bea435d8a080002810dc699b800298b46ff5f87352398ac4c697ffa55850b8f09b07f58a9aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52bb0fe7d2d617b8e83483497f7686c58703025c1ff7", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "35" + }, + { + "content-encoding": "gzip" + }, + { + "last-modified": "Wed, 21 Jan 2004 19:51:30 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "date": "Thu, 01 Nov 2012 14:30:09 GMT" + }, + { + "expires": "Wed, 19 Apr 2000 11:43:00 GMT" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "age": "168250" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, proxy-revalidate" + }, + { + "server": "GFE/2.0" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 126, + "wire": "88c30f0d023335c4f7c2c5c1bfc0be", + "headers": [ + { + ":status": "200" + }, + { + "date": "Thu, 01 Nov 2012 14:30:09 GMT" + }, + { + "content-length": "35" + }, + { + "x-content-type-options": "nosniff" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Wed, 19 Apr 2000 11:43:00 GMT" + }, + { + "last-modified": "Wed, 21 Jan 2004 19:51:30 GMT" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, proxy-revalidate" + }, + { + "age": "168250" + }, + { + "server": "GFE/2.0" + } + ] + }, + { + "seqno": 127, + "wire": "89c17b8b84842d695b05443c86aa6f5a839bd9abfbe70f0d0130cffaf9", + "headers": [ + { + ":status": "204" + }, + { + "content-type": "image/gif" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:14:19 GMT" + }, + { + "content-length": "0" + }, + { + "connection": "keep-alive" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "pragma": "no-cache" + } + ] + } + ], + "description": "Encoded by nghttp2. The basic encoding strategy is described in http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html We use huffman encoding only if it produces strictly shorter byte string than original. We make some headers not indexing at all, but this does not always result in less bits on the wire." +} \ No newline at end of file diff --git a/http/http-client/src/test/resources/hpack-test-case/story_29.json b/http/http-client/src/test/resources/hpack-test-case/story_29.json new file mode 100644 index 000000000..29839fd0d --- /dev/null +++ b/http/http-client/src/test/resources/hpack-test-case/story_29.json @@ -0,0 +1,14450 @@ +{ + "cases": [ + { + "seqno": 0, + "wire": "488264015f92497ca589d34d1f6a1271d882a60e1bf0acf70f1f8f9d29aee30c78f1e17a5152e43d2c7f768dd06258741e54ad9326e61d5dbf4089f2b567f05b0b22d1fa868776b5f4e0df6196dc34fd280654d27eea0801128166e09fb82754c5a37f0f0d820b42", + "headers": [ + { + ":status": "301" + }, + { + "content-type": "text/html; charset=UTF-8" + }, + { + "location": "http://www.msn.com/" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "date": "Sat, 03 Nov 2012 13:29:27 GMT" + }, + { + "content-length": "142" + } + ] + }, + { + "seqno": 1, + "wire": "88588da8eb10649cbf4a54759093d85f4085aec1cd48ff86a8eb10649cbf5f92497ca589d34d1f6a1271d882a60b532acf7f5a839bd9ab7b8b84842d695b05443c86aa6f4003703370afbdae0fe74ead2a70d3914bdab429a61e2a6edf0a99f55e52f70da352fe0e23535ee846a6bdd7c6a6ae1b54c9a6fff30f28d0ddb6f63e1bb6c10f0dfab6e0bf936c00f8c583571876c1f17f55d804008821033f6a17cd66b0a88341eafa500cada4fdd61002d28166e09fb827d4c5a37fda921e919aa817a5152e43d3f6a5634cf031408a2d961ec21e4290f6d49f055b303a305d4001738bbda99dd7b180200080100740892c9315621ea4d87a3f86a8eb2127b0bf6196dc34fd280654d27eea0801128166e09fb82794c5a37f0f0d84682e34f7", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "p3p": "CP=\"NON UNI COM NAV STA LOC CURa DEVa PSAa PSDa OUR IND\"" + }, + { + "set-cookie": "SRCHUSR=AUTOREDIR=0&GEOVAR=&DOB=20121103; expires=Mon, 03-Nov-2014 13:29:29 GMT; domain=.msn.com; path=/" + }, + { + "errorcodecount": "[0:0]" + }, + { + "s": "CO3SCH010020101" + }, + { + "edge-control": "no-store" + }, + { + "date": "Sat, 03 Nov 2012 13:29:28 GMT" + }, + { + "content-length": "41648" + } + ] + }, + { + "seqno": 2, + "wire": "88588aa47e561cc581a644007f5f87352398ac4c697f52848fd24a8f0f138efe5e03c49472b2408c8f06e03f9fcdcc7f06a9bdae0fe6ef0dca5ee1b54bdab49d4c3934a9938df3a9ab4e753570daa6bc7cd4dd0e83a9bf0673ff3f0f0d023433558375975a6196dc34fd280654d27eea0801128166e09fb827d4c5a37f6c96d07abe9413ea6a22541000ea816ee005702ca98b46ff6496dc34fd280654d27eea0801128266e09cb8cb6a62d1bf408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=43200" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"808cfaf3c1ac81:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "43" + }, + { + "age": "7374" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Mon, 29 Oct 2007 15:02:13 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 23:26:35 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 3, + "wire": "88588fa47e561cc581e71a003eabb63a0c4fc6c50f138efe40ebd21650b240ca4903701fcf768abda83a35ebddbef42077d4c50f0d02343355846596442fc46c96c361be94101486bb14100225041b8c86e09e53168dff6496dd6d5f4a01a5349fba820044a01ab816ae01d53168dfc3", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=86400,public" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"078def13c1fcd1:0\"" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "43" + }, + { + "age": "33322" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Fri, 20 Apr 2012 21:31:28 GMT" + }, + { + "expires": "Sun, 04 Nov 2012 04:14:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 4, + "wire": "88588ca47e561cc58190b6cb80003fcbca0f138efe40f3324af3f1b918c8f06e03f9768abb9f868d7af76fbd081ed9ca0f0d83085c1755867d979e13cd7fc96c96df697e941014d03f4a0800794102e05db8cb4a62d1bf6496e4593e940baa65b6850400b2a0837197ae01b53168dfc8", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"083df89b6bac81:0\"" + }, + { + "server": "BLUMPPSTCA08" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "1162" + }, + { + "age": "9388284" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Tue, 20 May 2008 20:17:34 GMT" + }, + { + "expires": "Wed, 17 Jul 2013 21:38:05 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 5, + "wire": "88c7cfce0f138efe40ebd21650b240ca4903701fcfc6dccd0f0d023433c5cbc4c3c8", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=86400,public" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"078def13c1fcd1:0\"" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "43" + }, + { + "age": "33322" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Fri, 20 Apr 2012 21:31:28 GMT" + }, + { + "expires": "Sun, 04 Nov 2012 04:14:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 6, + "wire": "88c2cfce0f138dfe40fb91b2f4817c840dc07f3fc6dccd0f0d033431375586085c6da700dfcc6c96df697e94032a681fa50400854102e322b82794c5a37f6496c361be941054cb6d4a08016540b9700e5c034a62d1bfcb", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"096b38d19cc1:0\"" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "417" + }, + { + "age": "11654605" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Tue, 03 May 2011 20:32:28 GMT" + }, + { + "expires": "Fri, 21 Jun 2013 16:06:04 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 7, + "wire": "88588aa47e561cc581c034f001d3d20f138ffe5f7dd79f95f94651bc4903701fcf768abda83a35ebddbef42073e1d27f198abda83a35ebddbef420730f0d8313a117558565d79f681fd26c96c361be940894d444a820044a05fb8205c1054c5a37ff6496df697e94038a693f750400894035702cdc69f53168dfd1", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"99789f9faea8cd1:0\"" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "2712" + }, + { + "age": "378940" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Fri, 12 Oct 2012 19:20:21 GMT" + }, + { + "expires": "Tue, 06 Nov 2012 04:13:49 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 8, + "wire": "88c35f88352398ac74acb37fd80f138ffe647248df75f75c2c6f9240dc07f3768abda83a35ebddbef4206fe7d87f048abda83a35ebddbef4206f0f0d83644e0b558465d7c0efd86c96dc34fd280654d27eea0801128015c6dab8d814c5a37f6496dc34fd281029a4fdd4100225002b8dbb71a1298b46ffd7", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/jpeg" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"d6db97976eb9cd1:0\"" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA05" + }, + { + "content-length": "3262" + }, + { + "age": "37907" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Sat, 03 Nov 2012 02:54:50 GMT" + }, + { + "expires": "Sat, 10 Nov 2012 02:57:42 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 9, + "wire": "88c9c3dd0f138ffe5c2086fc8d85d04612481b80fe7fc2ebdcc10f0d8369c69e5584132e3ccfdb6c96df697e94132a6a2254100225042b8d3b700253168dff6496dc34fd281029a4fdd410022500e5c6dab8d36a62d1bfda", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/jpeg" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"6c2a9d5170b1cd1:0\"" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA05" + }, + { + "content-length": "4648" + }, + { + "age": "23683" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Tue, 23 Oct 2012 22:47:02 GMT" + }, + { + "expires": "Sat, 10 Nov 2012 06:54:45 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 10, + "wire": "88d45f89352398ac7958c43d5fe10f138dfe40f884e361959789186e03f9ccefe00f0d83684f3955867d979e13efffdf6c96df697e94081486d994100205000b8066e000a62d1bff6496e4593e940baa65b6850400b2a08371976e36053168dfde", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/x-icon" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"0922651f38cb1:0\"" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "4286" + }, + { + "age": "9388299" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Tue, 10 Aug 2010 00:03:00 GMT" + }, + { + "expires": "Wed, 17 Jul 2013 21:37:50 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 11, + "wire": "88d0cae40f138ffe657e579f0351b638df2481b80fe7cff2e3ce0f0d836996c5558469c0b61fe26c96dc34fd280654d27eea0801128005c13f7191298b46ff6496dc34fd281029a4fdd4100225000b8d02e05e53168dffe1", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/jpeg" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"f9f8904b5ab9cd1:0\"" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "4352" + }, + { + "age": "46151" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Sat, 03 Nov 2012 00:29:32 GMT" + }, + { + "expires": "Sat, 10 Nov 2012 00:40:18 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 12, + "wire": "88d3cde70f138ffe5e699744e32479f8de2481b80fe7ccf5e6cb0f0d83702c875584132c859fe56c96df3dbf4a002a693f75040089413371966e004a62d1bf6496dc34fd281029a4fdd410022500edc002e36e298b46ffe4", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/jpeg" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"8437263c89b8cd1:0\"" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA05" + }, + { + "content-length": "6131" + }, + { + "age": "23313" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 23:33:02 GMT" + }, + { + "expires": "Sat, 10 Nov 2012 07:00:56 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 13, + "wire": "88d6d0ea0f138ffe5b686e342205a7a37c9206e03f9f768abda83a35ebddbef4207bf9ea7f108abda83a35ebddbef4207b0f0d836db10b5584132c81afea6c96c361be940094d27eea0801128215c13371b7d4c5a37f6496dc34fd281029a4fdd410022500edc006e01b53168dffe9", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/jpeg" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"54a642c148b9cd1:0\"" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "5522" + }, + { + "age": "23304" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 22:23:59 GMT" + }, + { + "expires": "Sat, 10 Nov 2012 07:01:05 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 14, + "wire": "88dbd5ef0f138ffe656e476572571e8de2481b80fe7fd4fdeed30f0d83680d3d5584132c89cfed6c96df3dbf4a002a693f7504008940bf7197ae05b53168df6496dc34fd281029a4fdd410022500edc002e34ca98b46ffec", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/jpeg" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"f5d7f6f68b8cd1:0\"" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA05" + }, + { + "content-length": "4048" + }, + { + "age": "23326" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 19:38:15 GMT" + }, + { + "expires": "Sat, 10 Nov 2012 07:00:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 15, + "wire": "88ded8f20f138efe5d79f784dc6cc6e3c4206e03f9c5ff01f10f0d83740eb75585640d38f87ff06c96e4593e94642a436cca08010a817ae09db8db4a62d1bf6496e4593e9403aa693f750400894002e361b81794c5a37fef", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/jpeg" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"789825b3b68cc1:0\"" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "7075" + }, + { + "age": "304691" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Wed, 31 Aug 2011 18:27:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 00:51:18 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 16, + "wire": "88e15f87352398ac5754dff60f138ffe5c1b4e05e91e6491be4903701fcfe14089f2b567f05b0b22d1fa868776b5f4e0dff6e10f0d84085b69af55846df03ceff56c96c361be940094d27eea0801128205c6deb8d32a62d1bf6496c361be9403ea693f750400894106e01ab8d094c5a37ff4", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"6a4618d83cb9cd1:0\"" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "11544" + }, + { + "age": "59087" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 20:58:43 GMT" + }, + { + "expires": "Fri, 09 Nov 2012 21:04:42 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 17, + "wire": "88e6e0fa0f138ffe42f3d23ee848cb91be4903701fcfcdc1f9cc0f0d8365d6d9c8f76c96c361be940094d27eea0801128205c082e32253168dffc7f5", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/jpeg" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"188d971c36b9cd1:0\"" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "3753" + }, + { + "age": "23326" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 20:10:32 GMT" + }, + { + "expires": "Sat, 10 Nov 2012 07:00:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 18, + "wire": "88e7fcfb0f138efe5e00df134c8e51bc4903701fcfe0c2fadf0f0d8310596f558471a7dc77f96c96c361be940894d444a820044a05fb826ae36ea98b46ff6496c361be9403ea693f7504008940bf704e5c684a62d1bff8", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80a9243afa8cd1:0\"" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA05" + }, + { + "content-length": "2135" + }, + { + "age": "64967" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Fri, 12 Oct 2012 19:24:57 GMT" + }, + { + "expires": "Fri, 09 Nov 2012 19:26:42 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 19, + "wire": "88eae4fe0f138ffe64189b7c6e0072c6f9240dc07f3fe9c5fde80f0d8371d79c55837df703fc6c96dc34fd280654d27eea0801128105c65eb8cb6a62d1bf6496dc34fd281029a4fdd4100225020b8d33704f298b46fffb", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/jpeg" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"da259a60afb9cd1:0\"" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "6786" + }, + { + "age": "9961" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Sat, 03 Nov 2012 10:38:35 GMT" + }, + { + "expires": "Sat, 10 Nov 2012 10:43:28 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 20, + "wire": "88ede7ff020f138ffe5918a396475c6de8df2481b80fe7f9c8ff017f148abda83a35ebddbef420770f0d8371e6df558469d69c6bff016c96dc34fd280654d27eea0801128005c0b9704e298b46ff6496dc34fd281029a4fdd4100225000b817ae09b53168dffff00", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/jpeg" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"3a2bfd7658b9cd1:0\"" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "6859" + }, + { + "age": "47464" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Sat, 03 Nov 2012 00:16:26 GMT" + }, + { + "expires": "Sat, 10 Nov 2012 00:18:25 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 21, + "wire": "88f1eb52848fd24a8f0f138efe652965195e6871be4903701fcffecd4003703370a9bdae0fe6ef0dca5ee1b54bdab49d4c3934a9938df3a9ab4e753570daa6bc7cd4dd0e83a9bf0673ff3fc30f0d83740e39d86196dc34fd280654d27eea0801128166e09fb827d4c5a37f6c96c361be940094d27eea0801128215c65fb82654c5a37fd8408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/jpeg" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"feefae84ab9cd1:0\"" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "7066" + }, + { + "age": "23304" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 22:39:23 GMT" + }, + { + "expires": "Sat, 10 Nov 2012 07:01:05 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 22, + "wire": "88f6f0c20f138ffe5a8c31beec8420237c9206e03f9ff5d1c1f40f0d840b4f89ef5583138eb9c16c96dc34fd280654d27eea0801128115c699b8d34a62d1bf6496dc34fd281029a4fdd4100225022b8d3571b654c5a37fc1", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/jpeg" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"4b1b97dcc0b9cd1:0\"" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "14928" + }, + { + "age": "2676" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Sat, 03 Nov 2012 12:43:44 GMT" + }, + { + "expires": "Sat, 10 Nov 2012 12:44:53 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 23, + "wire": "88ff025f86497ca582211f5a839bd9abc70f138ffe5e04ac85a24a1763749206e03f9f7b8b84842d695b05443c86aa6f768abda83a35ebddbef42077d8c80f0d84132f38d7558513e079c73fc86c96e4593e94642a6a225410022500cdc13d7196d4c5a37f6496df3dbf4a321535112a080165403571a0dc69953168dfc8", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "text/css" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80f314cf17b7cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "23864" + }, + { + "age": "290866" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 03:28:35 GMT" + }, + { + "expires": "Thu, 31 Oct 2013 04:41:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 24, + "wire": "88ff01facc0f138ffe5f23a190427c6cc6f9240dc07f3fff00dbcbfe0f0d8375a7dd55840b4e3c1fcb6c96dc34fd280654d27eea080112807ee043700fa98b46ff6496dc34fd281029a4fdd410022500fdc13571a794c5a37fcb", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/jpeg" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"9c71d229a3b9cd1:0\"" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "7497" + }, + { + "age": "14681" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "last-modified": "Sat, 03 Nov 2012 09:11:09 GMT" + }, + { + "expires": "Sat, 10 Nov 2012 09:24:48 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 25, + "wire": "885888a47e561cc58190035f87352398ac4c697f6c96c361be94036a6a225410022502fdc13f704f298b46ffd20f138efe401232dc6414a3649206e03f9f768dd06258741e54ad9326e61d5dbf54012ad36196dc34fd280654d27eea0801128166e09fb8c814c5a37f0f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=300" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Fri, 05 Oct 2012 19:29:28 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"01c35bc2fa3cd1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 26, + "wire": "88588aa47e561cc581a644007f5f911d75d0620d263d4c795ba0fb8d04b0d5a7d70f138ffe5f0b6f3cf0431c6f464903701fcfc2e6d60f0d83134207558468200bbfc16c96e4593e94036a6e2d6a0801128266e01cb82654c5a37f6496dc34fd280654d27eea080112816ae01bb8db2a62d1bfd6d1d0", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=43200" + }, + { + "content-type": "application/x-javascript" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"91588811bb8bcd1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "2420" + }, + { + "age": "41017" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Wed, 05 Sep 2012 23:06:23 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 14:05:53 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 27, + "wire": "88588ca47e561cc58190b6cb80003f5f88352398ac74acb37fd3dc0f138efe40569f923291b1949186e03f9fd2f7ebdb0f0d83680f0b558579c0b6eb81c66c96c361be9403aa651d4a08010a8266e361b80694c5a37f6496c361be94138a65b6850400b2a081702cdc13ea62d1bfdb", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"0e49dbec5aecb1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "4082" + }, + { + "age": "8615761" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Fri, 07 Jan 2011 23:51:04 GMT" + }, + { + "expires": "Fri, 26 Jul 2013 20:13:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 28, + "wire": "88588aa47e561cc581c034f001cde00f138efe4640b8e3d1ca46c44186e03f9ffbefdffa0f0d0336353755850b6fb2d3dfca6c96df3dbf4a084a6a22541000fa807ee34e5c65b53168df6496df3dbf4a01e5349fba820044a05db8166e34253168dfdf", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=604800" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"ac1668bfc52ca1:0\"" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "657" + }, + { + "age": "159348" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Thu, 22 Oct 2009 09:46:35 GMT" + }, + { + "expires": "Thu, 08 Nov 2012 17:13:42 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 29, + "wire": "88c6cae30f138ffe5e03238df18da71919240dc07f3fd8f2e20f0d84134e320f55867d979e13eeffcd6c96c361be94136a681fa5040089403b702fdc036a62d1bf6496e4593e940baa65b6850400b2a08371976e36ca98b46fe2dddc", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "application/x-javascript" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"803ab9aa463acd1:0\"" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "24630" + }, + { + "age": "9388297" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Fri, 25 May 2012 07:19:05 GMT" + }, + { + "expires": "Wed, 17 Jul 2013 21:37:53 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 30, + "wire": "88c9cde60f138efe4028e58dc189f8dd2481b80fe7768abda83a35ebddbef4206ff6e60f0d8471b71d07558513cdbc107fd16c96e4593e94642a6a225410022500ddc65ab8cbca62d1bf6496df3dbf4a321535112a0801654039700e5c0014c5a37fe6e1e0", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "application/x-javascript" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"02bfb6a29b7cd1:0\"" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "65670" + }, + { + "age": "285810" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 05:34:38 GMT" + }, + { + "expires": "Thu, 31 Oct 2013 06:06:00 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 31, + "wire": "88cdd7e1ea0f138efe5e048f3c37da71919240dc07f3e0dff9e90f0d0237345585780ebaf89bd46c96c361be94136a681fa5040089403b702fdc032a62d1bf6496c361be94009486d9941002ca800dc65db826d4c5a37fe9", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/gif" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80d88a9463acd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "74" + }, + { + "age": "8077925" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Fri, 25 May 2012 07:19:03 GMT" + }, + { + "expires": "Fri, 02 Aug 2013 01:37:25 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 32, + "wire": "88d0daed0f138ffe5e03eec8ebef34e3232481b80fe7e2fcec0f0d02343855867d979f75c6ffd76c96c361be94136a681fa5040089403b702f5c65b53168df6496e4593e940baa65b6850400b2a083702cdc136a62d1bfec", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"8097d798463acd1:0\"" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "48" + }, + { + "age": "9389765" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Fri, 25 May 2012 07:18:35 GMT" + }, + { + "expires": "Wed, 17 Jul 2013 21:13:25 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 33, + "wire": "88d3ff01e7f00f138ffe5b8c8cc92c810bb1ba4903701fcfe6768abda83a35ebddbef42073ff01f00f0d83704cb5558513e079c77fdb6c96e4593e94642a6a225410022500cdc13d7197d4c5a37fe5ef", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/png" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"5bc3dfd117b7cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "6234" + }, + { + "age": "290867" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 03:28:39 GMT" + }, + { + "expires": "Thu, 31 Oct 2013 04:41:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 34, + "wire": "88d6e0f30f138ffe5e004ae46f91a71919240dc07f3fe84089f2b567f05b0b22d1fa868776b5f4e0dff30f0d830842ef558579c0b4165fde6c96c361be94136a681fa5040089403b702f5c682a62d1bf6496c361be94138a65b6850400b2a08171a05c642a62d1bff3", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"801e6b9c463acd1:0\"" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "1117" + }, + { + "age": "8614139" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Fri, 25 May 2012 07:18:41 GMT" + }, + { + "expires": "Fri, 26 Jul 2013 20:40:31 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 35, + "wire": "88dae4f70f138efe40eba5946f34e3232481b80fe7768abda83a35ebddbef4207bc2f70f0d0234335586085b75a7dc6fe26c96c361be94136a681fa5040089403b702fdc0094c5a37f6496dc34fd28212996da941002ca816ae059b826d4c5a37ff7", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"077efa8463acd1:0\"" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "43" + }, + { + "age": "11574965" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Fri, 25 May 2012 07:19:02 GMT" + }, + { + "expires": "Sat, 22 Jun 2013 14:13:25 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 36, + "wire": "88dee8fb0f138ffe5e03eec8ebef34e3232481b80fe7c8c5fa0f0d83085a1755857d979e6400e5cb6496e4593e940baa65b6850400b2a08371976e36053168dff9", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"8097d798463acd1:0\"" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "1142" + }, + { + "age": "9388300" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Fri, 25 May 2012 07:18:35 GMT" + }, + { + "expires": "Wed, 17 Jul 2013 21:37:50 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 37, + "wire": "88e0eafd0f138ffe5e03ce4ad0db69c6464903701fcfcac7fc0f0d820ba2bfe66c96c361be94136a681fa5040089403b702f5c6dd53168dfbffa", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"8086f4a5463acd1:0\"" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "172" + }, + { + "age": "9388300" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Fri, 25 May 2012 07:18:57 GMT" + }, + { + "expires": "Wed, 17 Jul 2013 21:37:50 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 38, + "wire": "88e1e5fe0f138ffe5e005c95d1bad15c74840dc07f3ff3c8fd0f0d826841cee76c96df697e94640a436cca08010a817ee36d5c682a62d1bfcdfbf6f5", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "application/x-javascript" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"8016f7a74e67cc1:0\"" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "421" + }, + { + "age": "9389765" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Tue, 30 Aug 2011 19:54:41 GMT" + }, + { + "expires": "Wed, 17 Jul 2013 21:13:25 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 39, + "wire": "88e25f87352398ac5754dfff010f138efe5f1ba3944cba58db2481b80fe7f5caff000f0d836da65c558569c109e77fea6c96d07abe9413ea6a2254100225002b8cb7702053168dff6496df697e9413ea6a22541002ca806ee01ab8d32a62d1bfff00", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"9a7af237eb5cd1:0\"" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "5436" + }, + { + "age": "462287" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Mon, 29 Oct 2012 02:35:10 GMT" + }, + { + "expires": "Tue, 29 Oct 2013 05:04:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 40, + "wire": "885892a8eb10649cbf4a536a12b585ee3a0d20d25f5f92497ca589d34d1f6a5e9c7620a982d4cab3dff152848fd24a8f0f138efe401232dc6414a3649206e03f9ff1f04003703370a9bdae0fe6ef0dca5ee1b54bdab49d4c3934a9938df3a9ab4e753570daa6bc7cd4dd0e83a9bf0673ff3f6196dc34fd280654d27eea0801128166e09fb827d4c5a37f0f0d830b8d034085aec1cd48ff86a8eb10649cbf408a224a7aaa4ad416a9933f8365f79f6496c361be940054ca3a940bef814002e001700053168dff4086f2b58390d27f9bd61009c65d640b6f0800dbedb8f816bcd000000000000010b4203d5a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "text/html; Charset=utf-8" + }, + { + "last-modified": "Fri, 05 Oct 2012 19:29:28 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"01c35bc2fa3cd1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "content-length": "1640" + }, + { + "pragma": "no-cache" + }, + { + "cteonnt-length": "3989" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "x-radid": "P10263730-T100595690-C40000000000114208" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 41, + "wire": "88588da8eb10649cbf4a54759093d85fc35f92497ca589d34d1f6a1271d882a60b532acf7fc07b8b84842d695b05443c86aa6f7f08afbdae0fe74ead2a70d3914bdab429a61e2a6edf0a99f55e52f70da352fe0e23535ee846a6bdd7c6a6ae1b54c9a6fff30f28cdddb6f63bf068dd009b69c744ffc5f804db4e3a27fe21c3069d58756dd1f6a17cd66b0a88341eafa500cada4fdd61002d28166e09fb8c814c5a37fda921e919aa817a5152e43d3f6a5634cf031f408a2d961ec21e4290f6d49f055b303a305d4001738cbda99dd7b180200b2c800fff40892c9315621ea4d87a3f86a8eb2127b0bfca0f0d83105f77", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "p3p": "CP=\"NON UNI COM NAV STA LOC CURa DEVa PSAa PSDa OUR IND\"" + }, + { + "set-cookie": "SRCHD=MS=2546729&D=2546729&AF=NOFORM; expires=Mon, 03-Nov-2014 13:29:30 GMT; domain=.msn.com; path=/" + }, + { + "errorcodecount": "[0:0]" + }, + { + "s": "CO3SCH010133009" + }, + { + "edge-control": "no-store" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "content-length": "2197" + } + ] + }, + { + "seqno": 42, + "wire": "4882640258a1aec3771a4bf4a547588324e5fa52bb0fe7d2d617b8e83483497e94a8eb2127b0bfcb0f1fff339d29aee30c1171a64a52b90f4b045e634bfe5b21204d9697e24340cb40f8acd03ac85df8ad103ed8401f8a2a9a02d4b5a8f84d704e94d6ab30aa2c2a8b0f8f1e17a5152e43d2a8b0c859476d09f15959f0b8d15f9f8b0d2405644268242099095a109c8df0b6d3240b6d492331ba5f8b2a9200b2d85f69f65d0004cfc592c1f08259005971cf2eb8f7c6d2c97a022f4a2a5c87a7e347e61db0337dc64575bbadb2db8e005759704d8b0b6e36eb6e380c177f768dd06258741e54ad9326e61d5dbfe1ce0f28d3d1c325f819bee322baddd6d96dc7002bacb826c585b71b75b71c060bbf1bf864bf007ed490f48cd540bd28a9721e9fb50be6b3585441a0f57d280656d27eeb08016940b3704fdc640a62d1bfed4ac699e063efff010f0d0130", + "headers": [ + { + ":status": "302" + }, + { + "cache-control": "private, no-cache, proxy-revalidate, no-store" + }, + { + "pragma": "no-cache" + }, + { + "location": "http://c.atdmt.com/c.gif?udc=true&di=340&pi=7317&ps=95101&lng=en-us&tp=http%3A%2F%2Fwww.msn.com%2Fdefaultwpe3w.aspx&rid=e32241cc231e4226b91543c154dd3b7e&rnd=1351949370023&rf=&scr=1366x768&RedC=c.msn.com&MXFR=3D632B5B5356602B36252F56575660EB" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "set-cookie": "MUID=3D632B5B5356602B36252F56575660EB&TUID=1; domain=.msn.com; expires=Mon, 03-Nov-2014 13:29:30 GMT; path=/;" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "content-length": "0" + } + ] + }, + { + "seqno": 43, + "wire": "885886a8eb2127b0bf0f0d0234325f87352398ac4c697f5d9a9d29aee30c22b2ae34c94a5721e960d48e62a18acde4b42f31a5640130d1408721eaa8a4498f57842507417f", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store" + }, + { + "content-length": "42" + }, + { + "content-type": "image/gif" + }, + { + "content-location": "http://spe.atdmt.com/images/pixel.gif" + }, + { + "expires": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:29:29 GMT" + }, + { + "connection": "close" + } + ] + }, + { + "seqno": 44, + "wire": "c50f0d01306196dc34fd280654d27eea0801128166e09fb8c814c5a37f0f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c32e342640db01583e42bcc6975886a8eb10649cbfd37686c58703025c1f5f92497ca589d34d1f6a1271d882a60e1bf0acf7", + "headers": [ + { + ":status": "302" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "location": "http://s0.2mdn.net/viewad/3642305/1-1x1.gif" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "server": "GFE/2.0" + }, + { + "content-type": "text/html; charset=UTF-8" + } + ] + }, + { + "seqno": 45, + "wire": "885899a8eb10649cbf4a54759093d85fa529b5095ac2f71d0690692fd6c664022d316c96d07abe940bca65b68504008540bf700cdc65d53168dfdb0f138ffe4627d913ae38ec8d364206e03f9fca7f0f8be393068dda78e800020007c50f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001001" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 46, + "wire": "885889a47e561cc5802f001f5f8b497ca58e83ee3412c3569fd7de0f138efe40279d90b23b2c6f9240dc07f3d4768dd06258741e54ad9326e61d5c1f54012a0f0d830bc10f55830bae37ca6c96dc34fd280654d27eea080112806ae36f5c6dc53168df6496dc34fd280654d27eea0801128166e320b80694c5a37f7f0e88ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=1800" + }, + { + "content-type": "text/javascript" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"0287ded7fb9cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/7.0" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "1811" + }, + { + "age": "1765" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Sat, 03 Nov 2012 04:58:56 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 13:30:04 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 47, + "wire": "88c5c4dde40f138efe5e03a210dd201f78840dc07f3fdac3c20f0d837d969c5583085d07ce6c96df697e940054d27eea08010a817ae01ab807d4c5a37f6496dc34fd280654d27eea0801128166e340b800298b46ffc1", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=1800" + }, + { + "content-type": "text/javascript" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80722a7c098cc1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/7.0" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "9346" + }, + { + "age": "1170" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Tue, 01 Nov 2011 18:04:09 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 13:40:00 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 48, + "wire": "88c8c7e0e70f138efe403959786d900fbc4206e03f9fddc6c50f0d84101e03df55830b410fd16c96df697e940054d27eea08010a817ae01ab80714c5a37f6496dc34fd280654d27eea0801128166e32ddc6df53168dfc4", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=1800" + }, + { + "content-type": "text/javascript" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"0af38a5c098cc1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/7.0" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "20808" + }, + { + "age": "1411" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Tue, 01 Nov 2011 18:04:06 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 13:35:59 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 49, + "wire": "88cbcae3ea0f138ffe5e03e12b4523b2c6f9240dc07f3fe0c9c80f0d83085a1755830884e7d46c96dc34fd280654d27eea080112806ae36f5c6db53168df6496dc34fd280654d27eea0801128166e32fdc034a62d1bfc7", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=1800" + }, + { + "content-type": "text/javascript" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"8091e4ec7fb9cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/7.0" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "1142" + }, + { + "age": "1226" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Sat, 03 Nov 2012 04:58:55 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 13:39:04 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 50, + "wire": "deddea0f1fff349d29aee30c117a5152e43d2c11798d2ff96c8481365a5f890d032d03e2b340eb2177e2b440fb61007e28aa680b52d6a3e135c13a535aacc2a8b0aa2c3e3c785e9454b90f4aa2c321651db427c56567c2e3457e7e2c3490159109a09082642568427237c2db4c902db5248cc6e97e2caa4802cb617da7d97400133f164b07c209640165c73cbae3df1a3864bf032fde0bcd3376fbb7aeb8ebf800e099780cb97dabd75c74177e091c012491be475a0b426de8c1dcff00ec0f28d0ddb7445a2065fbc179a66edf76f5d71d7f001c132f01972fb57aeb8e82efda921e919aa808b8d325295c87a7ed42f9acd61510683d5f4a0195b49fbac2005a502cdc13f7190298b46ffb52b1a67818fbd60f0d0130", + "headers": [ + { + ":status": "302" + }, + { + "cache-control": "private, no-cache, proxy-revalidate, no-store" + }, + { + "pragma": "no-cache" + }, + { + "location": "http://c.msn.com/c.gif?udc=true&di=340&pi=7317&ps=95101&lng=en-us&tp=http%3A%2F%2Fwww.msn.com%2Fdefaultwpe3w.aspx&rid=e32241cc231e4226b91543c154dd3b7e&rnd=1351949370023&rf=&scr=1366x768&MUID=39C1843BD7CB679E06238036D4CB670B&cb=1cdb9c7414258b0" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "set-cookie": "SRM_M=39C1843BD7CB679E06238036D4CB670B; domain=c.atdmt.com; expires=Mon, 03-Nov-2014 13:29:30 GMT; path=/;" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "content-length": "0" + } + ] + }, + { + "seqno": 51, + "wire": "88ddeada6c96df3dbf4a080a6a2254100215020b8276e36f298b46ffee0f138efe40d4631965089e94840dc07f3fddff01ed0f28b9874ead37b1e6801f6a487a466aa022f4a2a5c87a7ed42f9acd615106fb4bf4a01c5b49fbac20044a059b827ee32053168dff6a5634cf031f7fd70f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-cache, proxy-revalidate, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 20 Oct 2011 10:27:58 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"04baaef128fcc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "set-cookie": "ANONCHK=0; domain=c.msn.com; expires=Tue, 06-Nov-2012 13:29:30 GMT; path=/;" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 52, + "wire": "88cf5f88352398ac74acb37fe8ef0f138efe44fbe37851c858df2481b80fe7e5cecd0f0d841000d39f5583081d6bd96c96c361be940094d27eea080112816ee09eb8d094c5a37f6496dc34fd280654d27eea0801128166e341b8cb8a62d1bfcc", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=1800" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"299a82bdeb9cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/7.0" + }, + { + "access-control-allow-origin": "*" + }, + { + "content-length": "20046" + }, + { + "age": "1074" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 15:28:42 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 13:41:36 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 53, + "wire": "890f0d0130dbccef6496d07abe940054ca3a940bef814002e001700053168dff58b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bf", + "headers": [ + { + ":status": "204" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "connection": "keep-alive" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + } + ] + }, + { + "seqno": 54, + "wire": "88e16c96c361be940baa436cca080112816ae09db8db6a62d1bf6196c361be940094d27eea080112817ee32d5c6de53168df6496dc34fd280654d27eea080112817ee32d5c6de53168df4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb768344b2970f0d023436408cf2b794216aec3a4a4498f57f8a0fda949e42c11d07275f558471a69d675890aed8e8313e94a47e561cc581e71a003f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Fri, 17 Aug 2012 14:27:55 GMT" + }, + { + "date": "Fri, 02 Nov 2012 19:34:58 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 19:34:58 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "46" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "64473" + }, + { + "cache-control": "public, max-age=86400" + } + ] + }, + { + "seqno": 55, + "wire": "886196dc34fd280654d27eea0801128076e001704ca98b46ff768bca54a7d7f4e2e15c42feff7f34cdacf4189eac2cb07f2c78648c567a0c4f4bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a97b86d1a90dfd0352fe0e23537c3906a6ae1b54bbc3729934df53869c8a5ed5a14d30f153269dffcffe7ec4089f2b567f05b0b22d1fa90d06b2c3d8a64a473154c9524b65454ff7c950ae152394c07020034a7f5a32645a1d779812e2fef408bf2b52632c419272ad3993f01310f0d0234324088ea52d6b0e83772ff8649a929ed4c027f1e88cc52d6b4341bb97f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 07:00:23 GMT" + }, + { + "server": "Jetty(6.1.22)" + }, + { + "p3p": "policyref=\"/w3c/policy.xml\", CP=\"NOI DSP COR CURa ADMa DEVa TAIa OUR BUS IND UNI COM NAV INT\"" + }, + { + "cache-control": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "x-powered-by": "Mirror Image Internet" + }, + { + "via": "1.1 bfi061004 (MII-APC/2.2)" + }, + { + "x-mii-cache-hit": "1" + }, + { + "content-length": "42" + }, + { + "keep-alive": "timeout=2" + }, + { + "connection": "Keep-Alive" + } + ] + }, + { + "seqno": 56, + "wire": "88588ca47e561cc58190b6cb80003ff2fe52848fd24a8f0f138efe405132d3e569c6464903701fcffc768abda83a35ebddbef420777f06868776b5f4e0df7f08a9bdae0fe6ef0dca5ee1b54bdab49d4c3934a9938df3a9ab4e753570daa6bc7cd4dd0e83a9bf0673ff3f0f0d0239335585780ebaf89c6196dc34fd280654d27eea0801128166e09fb8c854c5a37f6c96c361be94136a681fa5040089403b702f5c69a53168df6496c361be94009486d9941002ca800dc65db826d4c5a37fe7", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/gif" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"0e2349e463acd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "93" + }, + { + "age": "8077926" + }, + { + "date": "Sat, 03 Nov 2012 13:29:31 GMT" + }, + { + "last-modified": "Fri, 25 May 2012 07:18:44 GMT" + }, + { + "expires": "Fri, 02 Aug 2013 01:37:25 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 57, + "wire": "88768c86b19272ad78fe8e92b015c30f28e5aed9298a1861860d19edf246adc70e16f1e0a248529d3fee9dfa31b7433c309314bd39c3df46ee9a3478bfcb5b3beff0e5407ed4be7a466aa05ec2f7410cbd454fda983cd66b0a88375b57d280656d27eeb08016540b3704fdc642a62d1bfed4d634cf031ff64085aec1cd48ff86a8eb10649cbf6496df3dbf4a002a651d4a05f740a0017000b800298b46ff7f06e9acf4189eac2cb07f33a535dc618e885ec2f7410cbd454b1e19231620d5b35afe69a3f9fa52f6b83f9d3ab4a9af742a6bdd7d4c9c6153271bea6adfad4dd0e853269bea70d3914d7c36a97b568534c3c54c9a77a97f06852f69dea6edf0a9af6e05356fbca63c10ff3f4088f2b5761c8b48348f89ae46568e61a002581f5f9e1d75d0620d263d4c741f71a0961ab4fd9271d882a60c9bb52cf3cdbeb07f798624f6d5d4b27f5a839bd9ab7b8b84842d695b05443c86aa6f6196dc34fd280654d27eea0801128166e09fb8c814c5a37f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "set-cookie": "pudm_AAAA=MLuxc4uHAF5HEldAttN+mTMH5l3UFcGfjYAvMSjMMwDWP3TDUWl1; Domain=.revsci.net; Expires=Sun, 03-Nov-2013 13:29:31 GMT; Path=/" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "p3p": "policyref=\"http://js.revsci.net/w3c/rsip3p.xml\", CP=\"NON PSA PSD IVA IVD OTP SAM IND UNI PUR COM NAV INT DEM CNT STA PRE OTC HEA\"" + }, + { + "x-proc-data": "pd3-bgas02-0" + }, + { + "content-type": "application/javascript;charset=ISO-8859-1" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + } + ] + }, + { + "seqno": 58, + "wire": "88588da8eb10649cbf4a54759093d85fc75f92497ca589d34d1f6a1271d882a60b532acf7fc2c17f07afbdae0fe74ead2a70d3914bdab429a61e2a6edf0a99f55e52f70da352fe0e23535ee846a6bdd7c6a6ae1b54c9a6fff30f28d1ddb6f63bf06ed1007e346e804db4e3a27fe2fc026da71d13ff10e1834eac3ab6e8fb50be6b3585441a0f57d280656d27eeb08016940b3704fdc642a62d1bfed490f48cd540bd28a9721e9fb52b1a67818f408a2d961ec21e4290f6d49f055b303a305d7f3e8bbda99dd7b180200880113d40892c9315621ea4d87a3f86a8eb2127b0bfc40f0d830b8e8b", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "p3p": "CP=\"NON UNI COM NAV STA LOC CURa DEVa PSAa PSDa OUR IND\"" + }, + { + "set-cookie": "SRCHD=SM=1&MS=2546729&D=2546729&AF=NOFORM; expires=Mon, 03-Nov-2014 13:29:31 GMT; domain=.msn.com; path=/" + }, + { + "errorcodecount": "[0:0]" + }, + { + "s": "CO3SCH010120128" + }, + { + "edge-control": "no-store" + }, + { + "date": "Sat, 03 Nov 2012 13:29:30 GMT" + }, + { + "content-length": "1672" + } + ] + }, + { + "seqno": 59, + "wire": "88d65f911d75d0620d263d4c795ba0fb8d04b0d5a7d60f138efe403684018da71919240dc07f3f768abda83a35ebddbef42073d5d40f0d033338375586085c6da7422fd36c96c361be94136a681fa5040089403b702fdc034a62d1bf6496c361be941054cb6d4a08016540b9700d5c0bea62d1bffccbca", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "application/x-javascript" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"0a420aa463acd1:0\"" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "387" + }, + { + "age": "11654712" + }, + { + "date": "Sat, 03 Nov 2012 13:29:31 GMT" + }, + { + "last-modified": "Fri, 25 May 2012 07:19:04 GMT" + }, + { + "expires": "Fri, 21 Jun 2013 16:04:19 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 60, + "wire": "88dbf1cbda0f138efe40dc630be369c6464903701fcfcac1d8d70f0d8369e6855586085c6da6dd176196dc34fd280654d27eea0801128166e09fb8c894c5a37f6c96c361be94136a681fa5040089403b702f5c65e53168df6496c361be941054cb6d4a08016540b9700e5c680a62d1bf7f2188ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/jpeg" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"05ba19a463acd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "4842" + }, + { + "age": "11654572" + }, + { + "date": "Sat, 03 Nov 2012 13:29:32 GMT" + }, + { + "last-modified": "Fri, 25 May 2012 07:18:38 GMT" + }, + { + "expires": "Fri, 21 Jun 2013 16:06:40 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 61, + "wire": "88e05f87352398ac4c697fe00f138ffe5e046c89b1bad38c8c9206e03f9fc7dedd0f0d830840df558579c0b41683c36c96c361be94136a681fa5040089403b702f5c6df53168df6496c361be94138a65b6850400b2a08171a05c642a62d1bfc2", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80b325a7463acd1:0\"" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "1105" + }, + { + "age": "8614141" + }, + { + "date": "Sat, 03 Nov 2012 13:29:32 GMT" + }, + { + "last-modified": "Fri, 25 May 2012 07:18:59 GMT" + }, + { + "expires": "Fri, 26 Jul 2013 20:40:31 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 62, + "wire": "885892a8eb10649cbf4a536a12b585ee3a0d20d25f5f92497ca589d34d1f6a5e9c7620a982d4cab3df6c96c361be94036a6a225410022502fdc13f704f298b46ffe60f138efe401232dc6414a3649206e03f9f768dd06258741e54ad9326e61d5dbf54012ae5ca0f0d03353632df408a224a7aaa4ad416a9933f033838346496c361be940054ca3a940bef814002e001700053168dff4086f2b58390d27f9cd61038e3ec85e5b784006df6de6995af040f000000000000216dd10bdc", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "text/html; Charset=utf-8" + }, + { + "last-modified": "Fri, 05 Oct 2012 19:29:28 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"01c35bc2fa3cd1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:32 GMT" + }, + { + "content-length": "562" + }, + { + "pragma": "no-cache" + }, + { + "cteonnt-length": "884" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "x-radid": "P10669318-T100595843-C108000000000115722" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 63, + "wire": "88c5e27f018364216bc5c0c37f009cd61038065a034b6f0800dbedbadb8b5e69e00000000000042cb4f35fc3eae8de0f0d830b2267", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "cteonnt-length": "3114" + }, + { + "content-type": "text/html; Charset=utf-8" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-radid": "P10603404-T100595756-C48000000000113484" + }, + { + "access-control-allow-origin": "*" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:31 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "1323" + } + ] + }, + { + "seqno": 64, + "wire": "88c7c6c5ed0f138efe401232dc6414a3649206e03f9fc4c3eae80f0d830b2cb9e47f0083642d3bc27f009cd6103a2036d36b6f0800dbedbecbeb5e6c4000000000000880dbef7fe0", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "text/html; Charset=utf-8" + }, + { + "last-modified": "Fri, 05 Oct 2012 19:29:28 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"01c35bc2fa3cd1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:31 GMT" + }, + { + "content-length": "1336" + }, + { + "pragma": "no-cache" + }, + { + "cteonnt-length": "3147" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "x-radid": "P10720545-T100595939-C52000000000120598" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 65, + "wire": "88c9e67f0003343036c9c4c77f0094d6cbaf09f69a5b784006de13acbeb5e6c41138cfc7eeece20f0d03333235", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "cteonnt-length": "406" + }, + { + "content-type": "text/html; Charset=utf-8" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-radid": "P3782944-T100582739-C521263" + }, + { + "access-control-allow-origin": "*" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:31 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "325" + } + ] + }, + { + "seqno": 66, + "wire": "88f2cff10f138dfe411c8d85a942d0c8c86e03f9c8efee0f0d0238355586089e7da13aefd46c97df697e940b6a65b685040032a05cb8d3f719794c5a37ff6497c361be9403aa65b6a50400b2a01db8d3571b6d4c5a37ffd3e5e4", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/gif" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"0bd514f14ac31:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "content-length": "85" + }, + { + "age": "12894277" + }, + { + "date": "Sat, 03 Nov 2012 13:29:32 GMT" + }, + { + "last-modified": "Tue, 15 Jul 2003 16:49:38 GMT" + }, + { + "expires": "Fri, 07 Jun 2013 07:44:55 GMT" + }, + { + "connection": "keep-alive" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 67, + "wire": "885892aed8e8313e94a47e561cc58190b6cb6fbeffd3cc408cf2b0d15d454addcb620c7abf8769702ec8190bfff40f0d8313a107558665c6df65d07fd96496dd6d5f4a084a6e2d6a08016540377000b800a98b46ffd7", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, max-age=31535999" + }, + { + "content-type": "image/gif" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "2710" + }, + { + "age": "3659370" + }, + { + "date": "Sat, 03 Nov 2012 13:29:32 GMT" + }, + { + "expires": "Sun, 22 Sep 2013 05:00:01 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 68, + "wire": "885892aed8e8313e94a47e561cc58190b60782f3ff5f88352398ac74acb37fd1c2f80f0d837c4207558665c6df65b7bfdd6496dc34fd2820a9b8b5a8200595041b8172e34ca98b46ffdb", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, max-age=31508189" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "9220" + }, + { + "age": "3659358" + }, + { + "date": "Sat, 03 Nov 2012 13:29:32 GMT" + }, + { + "expires": "Sat, 21 Sep 2013 21:16:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 69, + "wire": "885892aed8e8313e94a47e561cc58190b2cbc075dfc1d4c5fb0f0d8365c03b558565a000220fe06496dd6d5f4a084a6e2d6a080165410ae005700f298b46ffde", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, max-age=31338077" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "3607" + }, + { + "age": "3400121" + }, + { + "date": "Sat, 03 Nov 2012 13:29:32 GMT" + }, + { + "expires": "Sun, 22 Sep 2013 22:02:08 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 70, + "wire": "886c96c361be940094d27eea080112807ee32ddc65953168df0f139afe42d042fbefbcf352b417ca378417e395f8db64237c8e39fcff52848fd24a8f5f9a1d75d0620d263d4c741f71a0961ab4fda849c7620a982d4cab3df2f30f0d83644e03e4e17f2fc6bdae0fe6f43a94bfbb5a99e1e4a5ee1b46a437f40d4bf8388d4df0e41a9af7423535eebe353271846a64e37c6a6ae1b54bbc3729934df53869c8a5ed5a14d30f153269dffcff", + "headers": [ + { + ":status": "200" + }, + { + "last-modified": "Fri, 02 Nov 2012 09:35:33 GMT" + }, + { + "etag": "\"1411999884f419ea8219bf9b531a9c66\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-type": "application/javascript; charset=utf-8" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "3260" + }, + { + "date": "Sat, 03 Nov 2012 13:29:32 GMT" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "CP=\"CAO DSP LAW CURa ADMa DEVa TAIa PSAa PSDa IVAa IVDa OUR BUS IND UNI COM NAV INT\"" + } + ] + }, + { + "seqno": 71, + "wire": "885f961d75d0620d263d4c7441eafb50938ec415305a99567b4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb0f0d023335e7e4", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/json; charset=utf-8" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "35" + }, + { + "date": "Sat, 03 Nov 2012 13:29:32 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 72, + "wire": "880f13a1fe5975d904dbb28a41144fb4f85c0b4c900e3e16824091bb81644f3acbc10b5fcf6c96e4593e9403ca612c6a080112820dc6dbb81694c5a37fc30f0d023433e45886a8eb10649cbfc2e9e6", + "headers": [ + { + ":status": "200" + }, + { + "etag": "\"377d257f2d2e294916143c069141c1c5:1328738114\"" + }, + { + "last-modified": "Wed, 08 Feb 2012 21:55:14 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "content-length": "43" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "no-cache" + }, + { + "p3p": "CP=\"CAO DSP LAW CURa ADMa DEVa TAIa PSAa PSDa IVAa IVDa OUR BUS IND UNI COM NAV INT\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:32 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 73, + "wire": "886c96dc34fd280654d27eea0801128166e04171b1298b46ff4085aec1cd48ff86a8eb10649cbf408bf2b4b60e92ac7ad263d48f89dd0e8c1ab6e4c5934fc3408442469b51851000a6acdf5f9a1d75d0620d263d4c741f71a0961ab4fd9271d882a60b532acf7ffd0f0d0338383976824ca5fd58ada8eb10649cbf4a54759093d85fa529b5095ac2f71d0690692fd2959d095893949d6007d295d855893949d6007f6496dc34fd280654d27eea0801128166e09fb8c894c5a37ff1ee", + "headers": [ + { + ":status": "200" + }, + { + "last-modified": "Sat, 03 Nov 2012 13:10:52 GMT" + }, + { + "pragma": "no-cache" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "x-content-type-options": "nosniff" + }, + { + "status": "200 OK" + }, + { + "content-type": "application/javascript;charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "889" + }, + { + "server": "tfe" + }, + { + "vary": "Accept-Encoding" + }, + { + "cache-control": "no-cache, no-store, must-revalidate, post-check=0, pre-check=0" + }, + { + "expires": "Sat, 03 Nov 2012 13:29:32 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:29:32 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 74, + "wire": "88ed4089f2b26c1d48191263d58c69f925684ecaeb4c95b7647b6c96dc34fd280654d27eea0801128166e09fb8c894c5a37f6496df697e94642a681d8a05f782a01bb8005c0014c5a37fc758ada8eb10649cbf4a54759093d85fa529b5095ac2f71d0690692fd295d855893949d6007d2959d095893949d6007f0f28c99ad2a1311a483b8556610b2d85f69f65d136ebac85b71cfb53079acd61510683d5f4a32b693f758400b4a059b827ee32253168dff6a6b1a67818fb52f9e919aa8174f832525b1721e9f55a839bd9ab0f0d023635c5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "x-transaction": "49df427f743e57d8" + }, + { + "last-modified": "Sat, 03 Nov 2012 13:29:32 GMT" + }, + { + "expires": "Tue, 31 Mar 1981 05:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0" + }, + { + "set-cookie": "guest_id=v1%3A135194937257731566; Expires=Mon, 3-Nov-2014 13:29:32 GMT; Path=/; Domain=.twitter.com" + }, + { + "date": "Sat, 03 Nov 2012 13:29:32 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "65" + }, + { + "server": "tfe" + } + ] + }, + { + "seqno": 75, + "wire": "886c96c361be940094d27eea080112807ee34f5c0b2a62d1bf0f139afe63032e0dd7c2f042596523ee3d1ca48da6651b9238f841fcffd25f92497ca589d34d1f6a1271d882a60b532acf7f7b8b84842d695b05443c86aa6fc10f0d83744d0bf9f6d2588faed8e8313e94a47e561cc5802f001f", + "headers": [ + { + ":status": "200" + }, + { + "last-modified": "Fri, 02 Nov 2012 09:48:13 GMT" + }, + { + "etag": "\"b036a791811effc968bfcb43fa6d6910\"" + }, + { + "accept-ranges": "bytes" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "7242" + }, + { + "date": "Sat, 03 Nov 2012 13:29:32 GMT" + }, + { + "connection": "keep-alive" + }, + { + "p3p": "CP=\"CAO DSP LAW CURa ADMa DEVa TAIa PSAa PSDa IVAa IVDa OUR BUS IND UNI COM NAV INT\"" + }, + { + "cache-control": "public, max-age=1800" + } + ] + }, + { + "seqno": 76, + "wire": "886196dc34fd280654d27eea0801128166e09fb8cb4a62d1bf768586b19272ff6c96dc34fd280654d27eea0801128172e09eb82714c5a37f6496e4593e9403aa693f7504008940b9704f5c138a62d1bf0f139d6822109a859132d6079e844eb8069c79965913a1004176e86f397c30c358a5a47e561cc58196db7590fd576c74189f551d64d83a9129eca7ea9b5095ac2f71d0690692ff408df2b1c88ad6b0b59ea90b62c693884bc5908339115b5f0f0d03343731408721eaa8a4498f57842507417f5f911d75d0620d263d4c1c88ad6b0a8acf520b", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:29:34 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Sat, 03 Nov 2012 16:28:26 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 16:28:26 GMT" + }, + { + "etag": "412224A3234E88A2760468333271010BB1C6D1AA" + }, + { + "cache-control": "max-age=355731,public,no-transform,must-revalidate" + }, + { + "x-ocsp-reponder-id": "t8edcaocsp4" + }, + { + "content-length": "471" + }, + { + "connection": "close" + }, + { + "content-type": "application/ocsp-response" + } + ] + }, + { + "seqno": 77, + "wire": "88be409221ea496a4ac9b0752252d8b16a21e435537f85ba6a8767af0f0d830becbd7f0184bd41d05f", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/ocsp-response" + }, + { + "content-transfer-encoding": "Binary" + }, + { + "content-length": "1938" + }, + { + "connection": "Close" + } + ] + }, + { + "seqno": 78, + "wire": "88c7c6c5c40f139d6822109a859132d6079e844eb8069c79965913a1004176e86f397c30c3c3c20f0d03343731c1c0", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:29:34 GMT" + }, + { + "server": "Apache" + }, + { + "last-modified": "Sat, 03 Nov 2012 16:28:26 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 16:28:26 GMT" + }, + { + "etag": "412224A3234E88A2760468333271010BB1C6D1AA" + }, + { + "cache-control": "max-age=355731,public,no-transform,must-revalidate" + }, + { + "x-ocsp-reponder-id": "t8edcaocsp4" + }, + { + "content-length": "471" + }, + { + "connection": "close" + }, + { + "content-type": "application/ocsp-response" + } + ] + }, + { + "seqno": 79, + "wire": "88588da8eb10649cbf4a54759093d85fd8cbcd64022d31cb768dd06258741e54ad9326e61e5c1f408ef2b0d15d454d3dc8b772d8831eaf03342e30408bf2b5a35887a6b1a4d1d05f8cc9820c124c5fb24f61e92c01e0dbef4089f2b567f05b0b22d1fa868776b5f4e0dfe16196dc34fd280654d27eea0801128166e09fb8cb6a62d1bf0f0d8413c00bdf", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "-1" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-aspnetmvc-version": "4.0" + }, + { + "x-ua-compatible": "IE=Edge;chrome=1" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "date": "Sat, 03 Nov 2012 13:29:35 GMT" + }, + { + "content-length": "28018" + } + ] + }, + { + "seqno": 80, + "wire": "886196dc34fd280654d27eea0801128072e34f5c6da53168df768bca54a7d7f4e2e15c42feff7f27cdacf4189eac2cb07f2c78648c567a0c4f4bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a97b86d1a90dfd0352fe0e23537c3906a6ae1b54bbc3729934df53869c8a5ed5a14d30f153269dffcffe35f87352398ac4c697f7f0490d06b2c3d8a64a473154c9524b65454ff7c950ae152394c070200054feb464c8b43aef3025c5fdf408bf2b52632c419272ad3993f01310f0d0234324088ea52d6b0e83772ff8649a929ed4c027f0e88cc52d6b4341bb97f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 06:48:54 GMT" + }, + { + "server": "Jetty(6.1.22)" + }, + { + "p3p": "policyref=\"/w3c/policy.xml\", CP=\"NOI DSP COR CURa ADMa DEVa TAIa OUR BUS IND UNI COM NAV INT\"" + }, + { + "cache-control": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "x-powered-by": "Mirror Image Internet" + }, + { + "via": "1.1 bfi061001 (MII-APC/2.2)" + }, + { + "x-mii-cache-hit": "1" + }, + { + "content-length": "42" + }, + { + "keep-alive": "timeout=2" + }, + { + "connection": "Keep-Alive" + } + ] + }, + { + "seqno": 81, + "wire": "88588ca47e561cc58190b6cb80003f5f86497ca582211fdef10f138ffe5e016324ad85b14602481b80fe7fdbcdcaed0f0d8371d0bb55850804e3817fca6c96d07abe941094d444a820044a0457197ee34ea98b46ff6496df697e941094d444a820059502e5c0bf702e298b46ff7f0488ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "text/css" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"80ebcf5152b0cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "6717" + }, + { + "age": "1026619" + }, + { + "date": "Sat, 03 Nov 2012 13:29:35 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 12:39:47 GMT" + }, + { + "expires": "Tue, 22 Oct 2013 16:19:16 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 82, + "wire": "88c35f901d75d0620d263d4c741f71a0961ab4ffe3f60f138efe4017db1c6db628c04903701fcfe0d2cff20f0d8379c6df55850804e38177cf6c96d07abe941094d444a820044a04571a15c65a53168dff6496df697e941094d444a820059502e5c0bf702f298b46ffc2", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "application/javascript" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"0195ab552b0cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "8659" + }, + { + "age": "1026617" + }, + { + "date": "Sat, 03 Nov 2012 13:29:35 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 12:42:34 GMT" + }, + { + "expires": "Tue, 22 Oct 2013 16:19:18 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 83, + "wire": "8858a1aec3771a4bf4a547588324e5fa52bb0fe7d2d617b8e83483497e94a8eb2127b0bff2ce6c96df3dbf4a080a6a2254100215020b8276e36f298b46fffb0f138efe40d4631965089e94840dc07f3f768dd06258741e54ad9326e61d5dbfd57f12a9bdae0fe6ef0dca5ee1b54bdab49d4c3934a9938df3a9ab4e753570daa6bc7cd4dd0e83a9bf0673ff3f0f28b9874ead37b1e6801f6a487a466aa022f4a2a5c87a7ed42f9acd615106fb4bf4a01c5b49fbac20044a059b827ee32053168dff6a5634cf031f7fd50f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-cache, proxy-revalidate, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 20 Oct 2011 10:27:58 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"04baaef128fcc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "set-cookie": "ANONCHK=0; domain=c.msn.com; expires=Tue, 06-Nov-2012 13:29:30 GMT; path=/;" + }, + { + "date": "Sat, 03 Nov 2012 13:29:35 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 84, + "wire": "88cb5f87352398ac5754dfeb52848fd24a8f0f138ffe5d03a375a191b14602481b80fe7fe9dbd8fb0f0d837d979fc6d76c96d07abe941094d444a820044a04571a15c0bea62d1bffc5c9", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/png" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"707a74ac52b0cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "9389" + }, + { + "age": "1026617" + }, + { + "date": "Sat, 03 Nov 2012 13:29:35 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 12:42:19 GMT" + }, + { + "expires": "Tue, 22 Oct 2013 16:19:18 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 85, + "wire": "88cec0edbf0f138ffe6501641c6f46d8a301240dc07f3feadcd9fc0f0d8365d69f55850804e38d37d96c96d07abe941094d444a820044a04571a0dc134a62d1bff6496df697e941094d444a820059502e5c0bd71b0298b46ffcc", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/png" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"f0edab8b52b0cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "3749" + }, + { + "age": "1026645" + }, + { + "date": "Sat, 03 Nov 2012 13:29:35 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 12:41:24 GMT" + }, + { + "expires": "Tue, 22 Oct 2013 16:18:50 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 86, + "wire": "88d1fb5f89352398ac7958c43d5ff1e1eee0dfde4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cbfc408cf2b0d15d454addcb620c7abf8769702ec8190bffdfbfde0f0d83684f396c96e4593e94642a6a225410022504cdc0b971b714c5a37fc60f138ffe5f6c2eb619051c91ba4903701fcf", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/x-icon" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "-1" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-aspnetmvc-version": "4.0" + }, + { + "x-ua-compatible": "IE=Edge;chrome=1" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "date": "Sat, 03 Nov 2012 13:29:35 GMT" + }, + { + "content-length": "4286" + }, + { + "last-modified": "Wed, 31 Oct 2012 23:16:56 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"951751d2bdb7cd1:0\"" + } + ] + }, + { + "seqno": 87, + "wire": "885899a8eb10649cbf4a54759093d85fa529b5095ac2f71d0690692f4085aec1cd48ff86a8eb10649cbfdde66c96d07abe940bca65b68504008540bf700cdc65d53168dfc90f138ffe4627d913ae38ec8d364206e03f9fcc4001738be393068dda78e800020007e30f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001001" + }, + { + "date": "Sat, 03 Nov 2012 13:29:35 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 88, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f7c3f5f88352398ac74acb37f768abda83a35ebddbef42077c6e7cf7f028abda83a35ebddbef420770f0d033737335585682e3aebbfe86496dc34fd280654d27eea0801128176e34cdc03ea62d1bfda", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431991" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "773" + }, + { + "age": "416777" + }, + { + "date": "Sat, 03 Nov 2012 13:29:35 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 17:43:09 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 89, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f79ffc3c2caebd3c10f0d8369d69d558471f7dd07eb6496e4593e9403aa693f7504008940bd700cdc0b4a62d1bfdd", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431989" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "4747" + }, + { + "age": "69970" + }, + { + "date": "Sat, 03 Nov 2012 13:29:35 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 18:03:14 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 90, + "wire": "88cac9e8f1c8d30f138ffe4627d913ae38ec8d364206e03f9fd67f048be393068dda78e800020035ed0f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001004" + }, + { + "date": "Sat, 03 Nov 2012 13:29:35 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 91, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034265f0b9fc7768abda83a35ebddbef4206fcff0d87f018abda83a35ebddbef4206f0f0d8469b089bf558369d7c3f16496df3dbf4a01e5349fba820044a01fb8db7700053168dfe3", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=423916" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA05" + }, + { + "content-length": "45125" + }, + { + "age": "4791" + }, + { + "date": "Sat, 03 Nov 2012 13:29:35 GMT" + }, + { + "expires": "Thu, 08 Nov 2012 09:55:00 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 92, + "wire": "885891aed8e8313ea91f95873160642db2e0000f0f0d846840701f5f911d75d0620d263d4c795ba0fb8d04b0d5a75a839bd9abdc0f138efe4232086595a70237c840dc07f37b8b84842d695b05443c86aa6ffa7f20dcbdae0fe61cf9d4c9a6fa97f76b52f6adaa437f4297b5693a97b86d52f70dc75327184ea64e37cea6bdd0a9af75f537c3914df8339d4d5c36a9ba1d0752f69dea5ed5a14c9a77a9a61e2a6ad39d4d78f9a9af6e0535f0daa70d393f9f4083ee91cd8d13afb8071c644d80000000007ff9558665f79d6de73f6196dc34fd280654d27eea0801128166e09fb8cb8a62d1bf6c96df697e941094d27eea08010a820dc6dfb80714c5a37f6496e4593e940bca6e2d6a080165403f71a7ee36053168dfed", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public,max-age=31536000" + }, + { + "content-length": "42060" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"1ac2aef461a9cc1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "p3p": "CP=\"ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI TELo OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI\"" + }, + { + "vtag": "279606632500000000" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "age": "3987586" + }, + { + "date": "Sat, 03 Nov 2012 13:29:36 GMT" + }, + { + "last-modified": "Tue, 22 Nov 2011 21:59:06 GMT" + }, + { + "expires": "Wed, 18 Sep 2013 09:49:50 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 93, + "wire": "890f0d0130fcedd96496d07abe940054ca3a940bef814002e001700053168dff58b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bf", + "headers": [ + { + ":status": "204" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:29:35 GMT" + }, + { + "connection": "keep-alive" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + } + ] + }, + { + "seqno": 94, + "wire": "885892a8eb10649cbf4a536a12b585ee3a0d20d25f5f92497ca589d34d1f6a5e9c7620a982d4cab3df6c96c361be94036a6a225410022502fdc13f704f298b46ffe80f138efe401232dc6414a3649206e03f9feb54012aebc60f0d03393534df408a224a7aaa4ad416a9933f831000f76496c361be940054ca3a940bef814002e001700053168dff4086f2b58390d27f9bd6103a265a6995b784006db038fb2b5e13e0000000000003c27040ce", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "text/html; Charset=utf-8" + }, + { + "last-modified": "Fri, 05 Oct 2012 19:29:28 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"01c35bc2fa3cd1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:36 GMT" + }, + { + "content-length": "954" + }, + { + "pragma": "no-cache" + }, + { + "cteonnt-length": "2008" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "x-radid": "P10723443-T100550693-C29000000000082620" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 95, + "wire": "885894a8eb2127b0bf4a547588324e5fa52bb0ddc692ffe36496dc34fd2816d4d27eea08007940b97000b800298b46ff7f0fe6acf4189eac2cb07f33a535dc61824952e392af285c87a58f0c918acf4189e98ad9ad7f34d1fcfd297b5c1fce9d5914bfbb5a97b56d521bfa14d7ba13a9af75f3a9ab86d3a9ba1d0753869da75356fda752ef0dca5ed5a14d30f152fe0d0a6edf0a9af6e0fe7f408cf2b794216aec3a4a4498f57f01300f28f61d5d20cd2db03d2e277ff9aef79b3cff6047ff3fe951678bfd7957760e2c4f565356d61d9c622e6e9fe3fd63083233bb76475f3f9feff8a317fe93ffcfb52b1a67818fb50be6b358544186c37d2800ad84b1ac20059502cdc13f719714c5a37fda921e919aa8171c957942e43d3f6a634a6bd5551ebf5f92497ca589d34d1f6a1271d882a60b532acf7fce0f0d03363135", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store, no-cache, private" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 15 Nov 2008 16:00:00 GMT" + }, + { + "p3p": "policyref=\"http://cdn.adnxs.com/w3c/policy/p3p.xml\", CP=\"NOI DSP COR ADM PSAo PSDo OURo SAMo UNRo OTRo BUS COM NAV DEM STA PRE\"" + }, + { + "x-xss-protection": "0" + }, + { + "set-cookie": "anj=Kfu=8fG7]PCxrx)0s]#%2L_'x%SEV/hnJip4FQV_eKj?9kb10I3SSI79ox)!lG@t]; path=/; expires=Fri, 01-Feb-2013 13:29:36 GMT; domain=.adnxs.com; HttpOnly" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "date": "Sat, 03 Nov 2012 13:29:36 GMT" + }, + { + "content-length": "615" + } + ] + }, + { + "seqno": 96, + "wire": "885886a8eb2127b0bf5f87497ca589d34d1fd5640130d56196dc34fd280654d27eea0801128166e09fb8cb6a62d1bf408721eaa8a4498f57842507417f0f0d03333831", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "text/html" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "0" + }, + { + "vary": "Accept-Encoding" + }, + { + "date": "Sat, 03 Nov 2012 13:29:35 GMT" + }, + { + "connection": "close" + }, + { + "content-length": "381" + } + ] + }, + { + "seqno": 97, + "wire": "885f87352398ac4c697f0f0d02343256034745546496df697e94038a693f75040089403571b0dc6dc53168dfd67f0288ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "42" + }, + { + "allow": "GET" + }, + { + "expires": "Tue, 06 Nov 2012 04:51:56 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:29:36 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 98, + "wire": "88cbf0cac9c80f28f61d5d20cd2db03d2e277ff9aef79b3cff6047ff3fe951678bfd7957760e2c4f565356d61d9c622e6e9fe3fd63083233bb76475f3f9feff8a317fe93ffcfb52b1a67818fb50be6b358544186c37d2800ad84b1ac20059502cdc13f719714c5a37fda921e919aa8171c957942e43d3f6a634a6bd5551ebf0f0d023433c1d7", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store, no-cache, private" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 15 Nov 2008 16:00:00 GMT" + }, + { + "p3p": "policyref=\"http://cdn.adnxs.com/w3c/policy/p3p.xml\", CP=\"NOI DSP COR ADM PSAo PSDo OURo SAMo UNRo OTRo BUS COM NAV DEM STA PRE\"" + }, + { + "x-xss-protection": "0" + }, + { + "set-cookie": "anj=Kfu=8fG7]PCxrx)0s]#%2L_'x%SEV/hnJip4FQV_eKj?9kb10I3SSI79ox)!lG@t]; path=/; expires=Fri, 01-Feb-2013 13:29:36 GMT; domain=.adnxs.com; HttpOnly" + }, + { + "content-length": "43" + }, + { + "content-type": "image/gif" + }, + { + "date": "Sat, 03 Nov 2012 13:29:36 GMT" + } + ] + }, + { + "seqno": 99, + "wire": "88c6c5dcc4db4086f2b5281c86938e640003cfb2f3ebb200004d38207fd8c30f0d83109f07", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "text/html" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "0" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-msadid": "300089389.300024620" + }, + { + "date": "Sat, 03 Nov 2012 13:29:36 GMT" + }, + { + "connection": "close" + }, + { + "content-length": "2290" + } + ] + }, + { + "seqno": 100, + "wire": "88ed0f0d84680ebcffc16496df3dbf4a01e5349fba820044a08371b76e34053168df6196dc34fd280654d27eea0801128166e09fb8cbaa62d1bfc1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "40789" + }, + { + "allow": "GET" + }, + { + "expires": "Thu, 08 Nov 2012 21:57:40 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 101, + "wire": "88e00f0d84101e00bfc36496d07abe94036a693f75040089403971905c684a62d1bfbfc2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/x-javascript" + }, + { + "content-length": "20802" + }, + { + "allow": "GET" + }, + { + "expires": "Mon, 05 Nov 2012 06:30:42 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 102, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f71eff1e7f84089f2b567f05b0b22d1fa868776b5f4e0df7f10a9bdae0fe6ef0dca5ee1b54bdab49d4c3934a9938df3a9ab4e753570daa6bc7cd4dd0e83a9bf0673ff3fe80f0d836990bf55830ba077c36496df3dbf4a01e5349fba820044a059b8005c65e53168dfc7", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431968" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA05" + }, + { + "content-length": "4319" + }, + { + "age": "1707" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Thu, 08 Nov 2012 13:00:38 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 103, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85d701ff6f5408cf2b0d15d454addcb620c7abf8769702ec8190bffc3c2f50f0d836d910755840b4f899fc76496df3dbf4a01e5349fba820044a01fb8172e36d298b46fcb", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431760" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "5321" + }, + { + "age": "14923" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Thu, 08 Nov 2012 09:16:54 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 104, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f7dcffa768abda83a35ebddbef42073c2c7c67f318abda83a35ebddbef420730f0d8369c037558469c65b7bcc6496df3dbf4a01e5349fba820044a00171972e36da98b46fd0", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431996" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "4605" + }, + { + "age": "46358" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Thu, 08 Nov 2012 00:36:55 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 105, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f137f5f88352398ac74acb37fc3c7cccbc20f0d83680fbb558471a65a6bd06496e4593e9403aa693f7504008940bf7196ee36f298b46fd4", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431925" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "4097" + }, + { + "age": "64344" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 19:35:58 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 106, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c880007fc1c6cacfcec50f0d8369c7c55584644f361fd36496df3dbf4a01e5349fba820044a01ab8215c038a62d1bfd7", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=432000" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "4692" + }, + { + "age": "32851" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Thu, 08 Nov 2012 04:22:06 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 107, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85d6dafc4c9cdd2d1c80f0d83136db755840b4f89afd66496df3dbf4a01e5349fba820044a01fb8172e34ea98b46fda", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431754" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "2555" + }, + { + "age": "14924" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Thu, 08 Nov 2012 09:16:47 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 108, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85d6defc7ccd0d5d4cb0f0d8368027355846da79f07d96496e4593e9403aa693f75040089410ae04171a6d4c5a37fdd", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431758" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "4026" + }, + { + "age": "54890" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 22:10:45 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 109, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f7ddfca768abda83a35ebddbef42077d4d9d87f108abda83a35ebddbef420770f0d8369b79f5585680c800effde6496dc34fd280654d27eea080112820dc64571a754c5a37fe2", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431997" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "4589" + }, + { + "age": "403007" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 21:32:47 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 110, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f7c1fcfc2d8dddcc10f0d8365a75c5584784113ffe16496e4593e9403aa693f7504008940b571a05c65e53168dfe5", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431990" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "3476" + }, + { + "age": "82129" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 14:40:38 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 111, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85e65ffd2d7dbe0dfd60f0d836df13b55846d969b6fe46496e4593e9403aa693f75040089410ae32e5c0054c5a37fe8", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431839" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "5927" + }, + { + "age": "53455" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 22:36:01 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 112, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f7d9fd5768abda83a35ebddbef4207bdfe4e37f098abda83a35ebddbef4207b0f0d8369f003558479979977e96496e4593e9403aa693f7504008940b57022b81654c5a37fed", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431993" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "4900" + }, + { + "age": "83837" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 14:12:13 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 113, + "wire": "88cdd9dee2e7e6dd0f0d8369a103558479e0381feb6496e4593e9403aa693f7504008940b37001b8db2a62d1bfef", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431997" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "4420" + }, + { + "age": "88061" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 13:01:53 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 114, + "wire": "885f901d75d0620d263d4c1c892a56426c28e90f0d831080fff26496df3dbf4a01e5349fba820044a05fb8276e36da98b46feef1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/octet-stream" + }, + { + "content-length": "2209" + }, + { + "allow": "GET" + }, + { + "expires": "Thu, 08 Nov 2012 19:27:55 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 115, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f785fdec6e7ecebc50f0d836da7dc55847190ba0ff06496e4593e9403aa693f7504008940bf71b72e09f53168dff4", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431982" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "5496" + }, + { + "age": "63170" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 19:56:29 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 116, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f7c3fe1c9eaefeec80f0d8365a6c1558479f6db6bf36496e4593e9403aa693f75040089408ae32e5c6da53168dff7", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431991" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "3450" + }, + { + "age": "89554" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 12:36:54 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 117, + "wire": "88d2e3e8ecf1f0e70f0d83109d0f55847c4fb81ff56496e4593e9403aa693f750400894086e340b80714c5a37ff9", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431990" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "2271" + }, + { + "age": "92961" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 11:40:06 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 118, + "wire": "88c2e5eaeef3f2e90f0d8368007f558479c642d7f76496e4593e9403aa693f7504008940b371905c6da53168dffb", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431991" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "4009" + }, + { + "age": "86314" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 13:30:54 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 119, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f79ffe8768abda83a35ebddbef4206ff2f7f67f118abda83a35ebddbef4206f0f0d8365d75f558479c740f7fc6496e4593e9403aa693f7504008940b3704d5c0bca62d1bf408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431989" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA05" + }, + { + "content-length": "3779" + }, + { + "age": "86708" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 13:24:18 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 120, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f75cfeed6f7fcfbd50f0d8364216f558471f0bce76196dc34fd280654d27eea0801128166e09fb8cbaa62d1bf6496e4593e9403aa693f7504008940bd702e5c03aa62d1bfc2", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431976" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "3115" + }, + { + "age": "69186" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 18:16:07 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 121, + "wire": "88e0f1e4fa4089f2b567f05b0b22d1fa868776b5f4e0df4003703370a9bdae0fe6ef0dca5ee1b54bdab49d4c3934a9938df3a9ab4e753570daa6bc7cd4dd0e83a9bf0673ff3fe50f0d8369e71d5584780e09afc26496e4593e9403aa693f7504008940b7700ddc69953168dfc6", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431990" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "4867" + }, + { + "age": "80624" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 15:05:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 122, + "wire": "88def5e8408cf2b0d15d454addcb620c7abf8769702ec8190bffc2c1e80f0d836c2f3b55850b40136cffc56496df697e94038a693f75040089410ae321b8dbaa62d1bfc9", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431993" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "5187" + }, + { + "age": "140253" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Tue, 06 Nov 2012 22:31:57 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 123, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f71cff9ecc1c5c4eb0f0d836c4d3555850b40782effc86496df697e94038a693f75040089410ae085700e298b46ffcc", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431966" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "5244" + }, + { + "age": "140817" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Tue, 06 Nov 2012 22:22:06 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 124, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f643ffcefc4c8c7ee0f0d8365e03b5585640cbeeb3fcb6496d07abe94036a693f750400894006e005702da98b46ffcf", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431931" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "3807" + }, + { + "age": "303973" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Mon, 05 Nov 2012 01:02:15 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 125, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f75df5f88352398ac74acb37f768abda83a35ebddbef42073c9cdcc7f168abda83a35ebddbef420730f0d83682cb755850b4cbccb7fd16496df697e94038a693f750400894106e321b8dbea62d1bfd5", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431977" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "4135" + }, + { + "age": "143835" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Tue, 06 Nov 2012 21:31:59 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 126, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f699fc3c2cdd1d0c10f0d836c0fb9558471a0b4ffd46496e4593e9403aa693f7504008940bf7197ee32153168dfd8", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431943" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "5096" + }, + { + "age": "64149" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 19:39:31 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 127, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f79efc6f0d0d4d3ef0f0d8365e0075585089b13adffd76496e4593e9403aa693f75040089400ae341b8c814c5a37fdb", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431988" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "3801" + }, + { + "age": "125275" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 02:41:30 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 128, + "wire": "88f9c8dfd2d6d5de0f0d8369a7df55850b4d05e17fd96496df697e94038a693f750400894106e09cb826d4c5a37fdd", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431990" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA05" + }, + { + "content-length": "4499" + }, + { + "age": "144182" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "expires": "Tue, 06 Nov 2012 21:26:25 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 129, + "wire": "885f961d75d0620d263d4c7441eafb50938ec415305a99567b4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb0f0d023335dcdf", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/json; charset=utf-8" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "35" + }, + { + "date": "Sat, 03 Nov 2012 13:29:37 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 130, + "wire": "88588da8eb10649cbf4a54759093d85f4085aec1cd48ff86a8eb10649cbf5f92497ca589d34d1f6a1271d882a60b532acf7f5a839bd9ab64022d317b8b84842d695b05443c86aa6f768dd06258741e54ad9326e61e5c1f408ef2b0d15d454d3dc8b772d8831eaf03342e30408bf2b5a35887a6b1a4d1d05f8cc9820c124c5fb24f61e92c01c7408bf2b4b60e92ac7ad263d48f89dd0e8c1ab6e4c5934fe0e4c86196dc34fd280654d27eea0801128166e09fb8d094c5a37f0f0d84132d34ff", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "-1" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-aspnetmvc-version": "4.0" + }, + { + "x-ua-compatible": "IE=Edge;chrome=1" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "date": "Sat, 03 Nov 2012 13:29:42 GMT" + }, + { + "content-length": "23449" + } + ] + }, + { + "seqno": 131, + "wire": "88588ca47e561cc58190b6cb80003f5f86497ca582211fc752848fd24a8f0f138efe4129637db75b8c44903701fcffc6c5e8cc0f0d836df6855585742e81a6ffc26c96e4593e94134a6a225410022502e5c659b8d3ca62d1bf6496dc34fd282714d444a820059500e5c0bd71b754c5a37ff0", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "text/css" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"0feb9575b2cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "5942" + }, + { + "age": "717045" + }, + { + "date": "Sat, 03 Nov 2012 13:29:42 GMT" + }, + { + "last-modified": "Wed, 24 Oct 2012 16:33:48 GMT" + }, + { + "expires": "Sat, 26 Oct 2013 06:18:57 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 132, + "wire": "88c35f901d75d0620d263d4c741f71a0961ab4ffccc20f138efe412bf2b2d34e4622481b80fe7fcac9ecd00f0d8379b13d5585742e81a6bfc66c96e4593e94134a6a225410022502e5c681704e298b46ff6496dc34fd282714d444a820059500e5c0bd71b794c5a37ff4", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "application/javascript" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"0f9f3446b2cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "8528" + }, + { + "age": "717044" + }, + { + "date": "Sat, 03 Nov 2012 13:29:42 GMT" + }, + { + "last-modified": "Wed, 24 Oct 2012 16:40:26 GMT" + }, + { + "expires": "Sat, 26 Oct 2013 06:18:58 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 133, + "wire": "88c75f87352398ac5754dfd0c60f138ffe5f71e1ba37db6c51809206e03f9fcecdf0d40f0d83782d3f55850804e38d87ca6c96d07abe941094d444a820044a04571a0dc680a62d1bff6496df697e941094d444a820059502e5c0bd71b0a98b46fff8", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/png" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"968a7a9552b0cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "8149" + }, + { + "age": "1026651" + }, + { + "date": "Sat, 03 Nov 2012 13:29:42 GMT" + }, + { + "last-modified": "Mon, 22 Oct 2012 12:41:40 GMT" + }, + { + "expires": "Tue, 22 Oct 2013 16:18:51 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 134, + "wire": "8858a1aec3771a4bf4a547588324e5fa52bb0fe7d2d617b8e83483497e94a8eb2127b0bfd65f87352398ac4c697f6c96df3dbf4a080a6a2254100215020b8276e36f298b46ffcc0f138efe40d4631965089e94840dc07f3f768dd06258741e54ad9326e61d5dbff7f60f28b9874ead37b1e6801f6a487a466aa022f4a2a5c87a7ed42f9acd615106fb4bf4a01c5b49fbac20044a059b827ee32053168dff6a5634cf031f7fd00f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-cache, proxy-revalidate, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 20 Oct 2011 10:27:58 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"04baaef128fcc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "set-cookie": "ANONCHK=0; domain=c.msn.com; expires=Tue, 06-Nov-2012 13:29:30 GMT; path=/;" + }, + { + "date": "Sat, 03 Nov 2012 13:29:42 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 135, + "wire": "88cfd95f89352398ac7958c43d5fd8d7d6d5d4d3dcd2f4f8dcd10f0d83684f396c96e4593e94642a6a225410022504cdc0b971b714c5a37fcf0f138ffe5f6c2eb619051c91ba4903701fcf", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/x-icon" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "-1" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-aspnetmvc-version": "4.0" + }, + { + "x-ua-compatible": "IE=Edge;chrome=1" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "date": "Sat, 03 Nov 2012 13:29:42 GMT" + }, + { + "content-length": "4286" + }, + { + "last-modified": "Wed, 31 Oct 2012 23:16:56 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"951751d2bdb7cd1:0\"" + } + ] + }, + { + "seqno": 136, + "wire": "885899a8eb10649cbf4a54759093d85fa529b5095ac2f71d0690692fdcc3d96c96d07abe940bca65b68504008540bf700cdc65d53168dfd10f138ffe4627d913ae38ec8d364206e03f9fc27f2c8be393068dda78e800020035d50f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001004" + }, + { + "date": "Sat, 03 Nov 2012 13:29:42 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 137, + "wire": "885892a8eb10649cbf4a536a12b585ee3a0d20d25f5f92497ca589d34d1f6a5e9c7620a982d4cab3df6c96c361be94036a6a225410022502fdc13f704f298b46ffd50f138efe401232dc6414a3649206e03f9fc654012a4003703370a9bdae0fe6ef0dca5ee1b54bdab49d4c3934a9938df3a9ab4e753570daa6bc7cd4dd0e83a9bf0673ff3fda0f0d03393534e3408a224a7aaa4ad416a9933f831000f76496c361be940054ca3a940bef814002e001700053168dff4086f2b58390d27f9bd6103a265a6995b784006db038fb2b5e13e0000000000003c27040e4", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "text/html; Charset=utf-8" + }, + { + "last-modified": "Fri, 05 Oct 2012 19:29:28 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"01c35bc2fa3cd1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:42 GMT" + }, + { + "content-length": "954" + }, + { + "pragma": "no-cache" + }, + { + "cteonnt-length": "2008" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "x-radid": "P10723443-T100550693-C29000000000082620" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 138, + "wire": "88c8e6cde3c7da0f138ffe4627d913ae38ec8d364206e03f9fcb7f078be393068dda78e800020007de0f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001001" + }, + { + "date": "Sat, 03 Nov 2012 13:29:42 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 139, + "wire": "88588eaed8e8313e94a47e561cc5804d7f5f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ed424e3b1054c16a6559efe7ce4085a4649cd5178ddda43f3f3f3f3f3f3f3f2db22f4087b0b5485b126a4b8f085813020044a3d702d5c0b2a43a3f4089f2b567f05b0b22d1fa868776b5f4e0df0f0d8465a7dd0f55023339e46c96c361be940094d27eea080112816ee05ab81654c5a37f6496dc34fd280654d27eea0801128166e34d5c0094c5a37f408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, max-age=24" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "machine": "SN1********532" + }, + { + "rendertime": "11/2/2012 8:14:13 AM" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "34971" + }, + { + "age": "39" + }, + { + "date": "Sat, 03 Nov 2012 13:29:42 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 15:14:13 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 13:44:02 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 140, + "wire": "885894a8eb2127b0bf4a547588324e5fa52bb0ddc692fff16496dc34fd2816d4d27eea08007940b97000b800298b46ff7f0ee6acf4189eac2cb07f33a535dc61824952e392af285c87a58f0c918acf4189e98ad9ad7f34d1fcfd297b5c1fce9d5914bfbb5a97b56d521bfa14d7ba13a9af75f3a9ab86d3a9ba1d0753869da75356fda752ef0dca5ed5a14d30f152fe0d0a6edf0a9af6e0fe7f408cf2b794216aec3a4a4498f57f01300f28f91d5d20cd2db03d2e267cc17bcd9e7fb023ff9ff4a8b3c5febcabbb071627ab35afb58767188b9ba7f8ff58c20c8ceedd91db80f18fdffebfbc5fe7f666bf67cdf6a5634cf031f6a17cd66b0a8830d86fa50015b096358400b2a059b827ee34ca98b46ffb5243d2335502e392af285c87a7ed4c694d7aaaa3d7f36196dc34fd280654d27eea0801128166e09fb8d32a62d1bf0f0d03353134", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store, no-cache, private" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 15 Nov 2008 16:00:00 GMT" + }, + { + "p3p": "policyref=\"http://cdn.adnxs.com/w3c/policy/p3p.xml\", CP=\"NOI DSP COR ADM PSAo PSDo OURo SAMo UNRo OTRo BUS COM NAV DEM STA PRE\"" + }, + { + "x-xss-protection": "0" + }, + { + "set-cookie": "anj=Kfu=8fG3x=Cxrx)0s]#%2L_'x%SEV/hnKu94FQV_eKj?9kb10I3SSI7:0wHz@)G?)i4ZhK; path=/; expires=Fri, 01-Feb-2013 13:29:43 GMT; domain=.adnxs.com; HttpOnly" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "content-length": "514" + } + ] + }, + { + "seqno": 141, + "wire": "885891aed8e8313e94a47e561cc581d75d6da71d5f91497ca582211f6a1271d882a60b532acf7ff5dccbcac90f0d8413a2103f5585109c69f7ffc16c96e4593e94642a6a2254100225042b826ee36253168dff6496df697e9413ea651d4a080165410ae09bb8d854c5a37fc8408cf2b0d15d454addcb620c7abf8769702ec8190bff", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, max-age=7775467" + }, + { + "content-type": "text/css; charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "machine": "SN1********532" + }, + { + "rendertime": "11/2/2012 8:14:13 AM" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "27220" + }, + { + "age": "226499" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 22:25:52 GMT" + }, + { + "expires": "Tue, 29 Jan 2013 22:25:51 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-aspnet-version": "4.0.30319" + } + ] + }, + { + "seqno": 142, + "wire": "885885aed8e8313f0f0d830b4e37e8f0e1d87f008712e05db03a277fcf558475d69b07c76c96df3dbf4a01c53716b5040089403371a15c0b8a62d1bf6496c361be940b8a693f7504008940b771b7ae36d298b46fce", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public" + }, + { + "content-length": "1465" + }, + { + "content-type": "image/png" + }, + { + "accept-ranges": "bytes" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "age": "77450" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "last-modified": "Thu, 06 Sep 2012 03:42:16 GMT" + }, + { + "expires": "Fri, 16 Nov 2012 15:58:54 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 143, + "wire": "885886a8eb2127b0bf5f87497ca589d34d1f5a839bd9ab6401307b8b84842d695b05443c86aa6f4086f2b5281c86938e640003cfb4d01713efbecb4f34f7cf7f15842507417f0f0d03353134", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "text/html" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "0" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-msadid": "300089440.299934848" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "connection": "close" + }, + { + "content-length": "514" + } + ] + }, + { + "seqno": 144, + "wire": "88c95f88352398ac74acb37fede4c9da0f0d8365f65c55830b8e35d26c96df3dbf4a002a693f750400894086e36e5c684a62d1bf6496dc34fd280654d27eea080112816ae32ddc0054c5a37fd9", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "3936" + }, + { + "age": "1664" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 11:56:42 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 14:35:01 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 145, + "wire": "88cdc1f0e7ccdd0f0d8369c69c55830b8db3d56c96c361be940094d27eea0801128015c685704d298b46ff6496dc34fd280654d27eea0801128176e05ab8d014c5a37fdc", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "4646" + }, + { + "age": "1653" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 02:42:24 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 17:14:40 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 146, + "wire": "88c40f0d840b4ebc0f56034745546496c361be9403ea693f75040089403571a6ee36d298b46fd9de", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "14780" + }, + { + "allow": "GET" + }, + { + "expires": "Fri, 09 Nov 2012 04:45:54 GMT" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 147, + "wire": "880f0d023433d9de4085aec1cd48ff86a8eb10649cbf6496d07abe940054ca3a940bef814002e001700053168dff58b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bffa", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 148, + "wire": "88d5c9f8efd4e50f0d84682171bfc8dc6c96c361be940094d27eea0801128015c6c5719694c5a37f6496dc34fd280654d27eea080112816ae321b8dbea62d1bfe3", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "41165" + }, + { + "age": "1664" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 02:52:34 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 14:31:59 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 149, + "wire": "885f961d75d0620d263d4c7441eafb50938ec415305a99567b4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb0f0d023335e0e5", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/json; charset=utf-8" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "35" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 150, + "wire": "488264025885aec3771a4b5f92497ca589d34d1f6a1271d882a60b532acf7f0f1fbe9d29aee30c52b8e4abca1721e962944a921cfd4c59c7549416cff13007e09068e192faacc8cbf782f34cddbeedebae3afe0038265e032e5f6af5d71d05df768dd06258741e54ad9326e61d5dbfdcedf6e40f0d023133", + "headers": [ + { + ":status": "302" + }, + { + "cache-control": "private" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "location": "http://m.adnxs.com/msftcookiehandler?t=1&c=MUID%3d39C1843BD7CB679E06238036D4CB670B" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "content-length": "13" + } + ] + }, + { + "seqno": 151, + "wire": "88e8c8e7e6e50f28bd41508803f6a5634cf031f6a17cd66b0a88375b57d280696d27eeb0801128166e09fb8d32a62d1bfed490f48cd540b8e4abca1721e9fb531a535eaaa8f55f87352398ac4c697fe50f0d023433", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store, no-cache, private" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 15 Nov 2008 16:00:00 GMT" + }, + { + "p3p": "policyref=\"http://cdn.adnxs.com/w3c/policy/p3p.xml\", CP=\"NOI DSP COR ADM PSAo PSDo OURo SAMo UNRo OTRo BUS COM NAV DEM STA PRE\"" + }, + { + "x-xss-protection": "0" + }, + { + "set-cookie": "sess=1; path=/; expires=Sun, 04-Nov-2012 13:29:43 GMT; domain=.adnxs.com; HttpOnly" + }, + { + "content-type": "image/gif" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "content-length": "43" + } + ] + }, + { + "seqno": 152, + "wire": "88ded2bff8ddee0f0d8374407355830bce87e66c96df697e94640a6a225410022502fdc106e080a62d1bff6496dc34fd280654d27eea080112816ae34e5c0b2a62d1bfed", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "7206" + }, + { + "age": "1871" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "last-modified": "Tue, 30 Oct 2012 19:21:20 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 14:46:13 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 153, + "wire": "88e1d5c2fbe0f10f0d8313ccb355836990bfe96c96e4593e94642a6a2254100225001b8215c65d53168dff6496dc34fd280654d27eea0801128166e36edc0baa62d1bff0", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "2833" + }, + { + "age": "4319" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 01:22:37 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 13:57:17 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 154, + "wire": "88e4d8c5fee3f40f0d83682d07c3eb6c96df3dbf4a002a693f75040089403f702d5c0b8a62d1bf6496dc34fd280654d27eea080112816ae34edc134a62d1bff2", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "4141" + }, + { + "age": "1871" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 09:14:16 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 14:47:24 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 155, + "wire": "88588eaed8e8313e94a47e561cc581c003c9e06496dc34fd280654d27eea0801128166e32fdc69953168dfdfc97f3b8ddda43f3f3f3f3f3f3f3f2d89ff7f3b8f08586581002251cb827ee34ca90e8f4089f2b585aa42d893525f8702e00baa20a447fbf20f0d8371d7c1f7", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, max-age=600" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "Sat, 03 Nov 2012 13:39:43 GMT" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "machine": "SN1********529" + }, + { + "rendertime": "11/3/2012 6:29:43 AM" + }, + { + "x-rendertime": "0.017 secs" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "content-length": "6790" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 156, + "wire": "88ebdfcc54012aebfc0f0d83744e355583699107f46c96c361be940094d27eea0801128015c69bb8cb2a62d1bf6496dc34fd280654d27eea0801128166e34edc684a62d1bffb", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "7264" + }, + { + "age": "4321" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 02:45:33 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 13:47:42 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 157, + "wire": "885891aed8e8313e94a47e561cc581d75d7000075f87352398ac5754dfead27f078ddda43f3f3f3f3f3f3f3f2db22f7f078f085813020044a3d702d5c0b2a43a3f4089f2b567f05b0b22d1fa868776b5f4e0df0f0d83640f335584109d08036196dc34fd280654d27eea0801128166e09fb8d34a62d1bf6c96e4593e94642a6a2254100225042b826ae34ca98b46ff6496df697e9413ea651d4a080165410ae09ab8d32a62d1bf7f2e88ea52d6b0e83772fffa", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, max-age=7776000" + }, + { + "content-type": "image/png" + }, + { + "content-encoding": "gzip" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "machine": "SN1********532" + }, + { + "rendertime": "11/2/2012 8:14:13 AM" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "3083" + }, + { + "age": "227101" + }, + { + "date": "Sat, 03 Nov 2012 13:29:44 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 22:24:43 GMT" + }, + { + "expires": "Tue, 29 Jan 2013 22:24:43 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-aspnet-version": "4.0.30319" + } + ] + }, + { + "seqno": 158, + "wire": "885891aed8e8313e94a47e561cc581d75d6df79fff00f3dbc6c5c40f0d033633315585109d0b4d7fc36c96e4593e94642a6a2254100225042b8266e36053168dff6496df697e9413ea651d4a080165410ae099b8d3ea62d1bfc2fe", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, max-age=7775989" + }, + { + "content-type": "text/css; charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "machine": "SN1********532" + }, + { + "rendertime": "11/2/2012 8:14:13 AM" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "631" + }, + { + "age": "227144" + }, + { + "date": "Sat, 03 Nov 2012 13:29:44 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 22:23:50 GMT" + }, + { + "expires": "Tue, 29 Jan 2013 22:23:49 GMT" + }, + { + "connection": "keep-alive" + }, + { + "x-aspnet-version": "4.0.30319" + } + ] + }, + { + "seqno": 159, + "wire": "88fdf1decffcc70f0d836d971c5583105c7fc66c96df3dbf4a002a693f75040089413371a72e01b53168df6496dc34fd280654d27eea080112816ae099b8cb6a62d1bfc5", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "5366" + }, + { + "age": "2169" + }, + { + "date": "Sat, 03 Nov 2012 13:29:44 GMT" + }, + { + "last-modified": "Thu, 01 Nov 2012 23:46:05 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 14:23:35 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 160, + "wire": "885891aed8e8313e94a47e561cc581d75d6df7835f9c1d75d0620d263d4c795ba0fb8d04b0d5a7ed424e3b1054c16a6559effbe3408cf2b0d15d454addcb620c7abf8769702ec8190bffcd0f0d836c226fc6cb6c96e4593e94642a6a2254100225042b8266e34153168dff6496df697e9413ea651d4a080165410ae099b8d054c5a37fca", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, max-age=7775981" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "5125" + }, + { + "age": "227144" + }, + { + "date": "Sat, 03 Nov 2012 13:29:44 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 22:23:41 GMT" + }, + { + "expires": "Tue, 29 Jan 2013 22:23:41 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 161, + "wire": "885885aed8e8313ffae7d87f028712e05db03a277fd10f0d8465e6da6bfacf6c96c361be940094d27eea0801128015c69cb81794c5a37f6496dc34fd280654d27eea080112816ae322b800298b46ffce", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "x-aspnet-version": "2.0.50727" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "38544" + }, + { + "age": "1664" + }, + { + "date": "Sat, 03 Nov 2012 13:29:44 GMT" + }, + { + "last-modified": "Fri, 02 Nov 2012 02:46:18 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 14:32:00 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 162, + "wire": "885891aed8e8313e94a47e561cc581d75d6df705c65a839bd9abecc6d50f0d8471e69a775585109d03ce7fd46c96e4593e94642a6a2254100225042b826ae082a62d1bff6496df697e9413ea651d4a080165410ae09ab820298b46ffd3", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, max-age=7775962" + }, + { + "content-type": "application/x-javascript; charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "content-length": "68447" + }, + { + "age": "227086" + }, + { + "date": "Sat, 03 Nov 2012 13:29:44 GMT" + }, + { + "last-modified": "Wed, 31 Oct 2012 22:24:21 GMT" + }, + { + "expires": "Tue, 29 Jan 2013 22:24:20 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 163, + "wire": "885886a8eb10649cbff1c264022d317b8b84842d695b05443c86aa6ff27f1e8ddda43f3f3f3f3f3f3f3f2d89bf7f1e8f08586581002251cb827ee34d290e8f7f278702e0032a20a447de6196dc34fd280654d27eea0801128166e09fb8d32a62d1bf0f0d8308997bda4085aec1cd48ff86a8eb10649cbf", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "-1" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "machine": "SN1********525" + }, + { + "rendertime": "11/3/2012 6:29:44 AM" + }, + { + "x-rendertime": "0.003 secs" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "date": "Sat, 03 Nov 2012 13:29:43 GMT" + }, + { + "content-length": "1238" + }, + { + "connection": "keep-alive" + }, + { + "pragma": "no-cache" + } + ] + }, + { + "seqno": 164, + "wire": "88c5bef8c9c4c3f77f038ddda43f3f3f3f3f3f3f3f2d81ffc20f28b5f66ae025c27bfb5243d233550528a9721e9fb50be6b3585441b869fa50205b49fbac20044a05ab827ee34d298b46ffb52b1a67818f7f028702e071d5105223e2e00f0d03313338dd", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "-1" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "machine": "SN1********509" + }, + { + "rendertime": "11/3/2012 6:29:44 AM" + }, + { + "set-cookie": "zip=c:cz; domain=msn.com; expires=Sat, 10-Nov-2012 14:29:44 GMT; path=/" + }, + { + "x-rendertime": "0.067 secs" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "date": "Sat, 03 Nov 2012 13:29:44 GMT" + }, + { + "content-length": "138" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 165, + "wire": "88588da8eb10649cbf4a54759093d85fc1fbccc7c6768dd06258741e54ad9326e61e5c1f408ef2b0d15d454d3dc8b772d8831eaf03342e30408bf2b5a35887a6b1a4d1d05f8cc9820c124c5fb24f61e92c01ff02408bf2b4b60e92ac7ad263d48f89dd0e8c1ab6e4c5934fd8e74090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb6196dc34fd280654d27eea0801128166e09fb8d38a62d1bf0f0d84136e320f6c96e4593e94642a6a225410022504cdc0b971b714c5a37f52848fd24a8f0f138ffe5f6c2eb619051c91ba4903701fcf", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "-1" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-aspnetmvc-version": "4.0" + }, + { + "x-ua-compatible": "IE=Edge;chrome=1" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "date": "Sat, 03 Nov 2012 13:29:46 GMT" + }, + { + "content-length": "25630" + }, + { + "last-modified": "Wed, 31 Oct 2012 23:16:56 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"951751d2bdb7cd1:0\"" + } + ] + }, + { + "seqno": 166, + "wire": "8858a1aec3771a4bf4a547588324e5fa52bb0fe7d2d617b8e83483497e94a8eb2127b0bfca5f87352398ac4c697f6c96df3dbf4a080a6a2254100215020b8276e36f298b46ffc10f138efe40d4631965089e94840dc07f3f768dd06258741e54ad9326e61d5dbfef4003703370a9bdae0fe6ef0dca5ee1b54bdab49d4c3934a9938df3a9ab4e753570daa6bc7cd4dd0e83a9bf0673ff3f0f28b9874ead37b1e6801f6a487a466aa022f4a2a5c87a7ed42f9acd615106fb4bf4a01c5b49fbac20044a059b827ee32053168dff6a5634cf031f7f6196dc34fd280654d27eea0801128166e09fb8d3aa62d1bf0f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-cache, proxy-revalidate, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 20 Oct 2011 10:27:58 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"04baaef128fcc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "set-cookie": "ANONCHK=0; domain=c.msn.com; expires=Tue, 06-Nov-2012 13:29:30 GMT; path=/;" + }, + { + "date": "Sat, 03 Nov 2012 13:29:47 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 167, + "wire": "885899a8eb10649cbf4a54759093d85fa529b5095ac2f71d0690692fd0c3d66c96d07abe940bca65b68504008540bf700cdc65d53168dfc60f138ffe4627d913ae38ec8d364206e03f9fc24001738be393068dda78e800020007c10f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001001" + }, + { + "date": "Sat, 03 Nov 2012 13:29:47 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 168, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b0342136f39f5f88352398ac74acb37f768abda83a35ebddbef42073e8f7c57f028abda83a35ebddbef420730f0d033831385585644169e7bfc66497dd6d5f4a01a5349fba820044a05db8cb571a6d4c5a37fff5", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=422586" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "818" + }, + { + "age": "321488" + }, + { + "date": "Sat, 03 Nov 2012 13:29:47 GMT" + }, + { + "expires": "Sun, 04 Nov 2012 17:34:45 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 169, + "wire": "885892a8eb10649cbf4a536a12b585ee3a0d20d25f5f92497ca589d34d1f6a5e9c7620a982d4cab3df6c96c361be94036a6a225410022502fdc13f704f298b46ffd00f138efe401232dc6414a3649206e03f9fcc54012acccb0f0d03393534dc408a224a7aaa4ad416a9933f830befb96496c361be940054ca3a940bef814002e001700053168dff4086f2b58390d27f9bd6103a265a6995b784006db038fb2b5e13e0000000000003c27040ea", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "text/html; Charset=utf-8" + }, + { + "last-modified": "Fri, 05 Oct 2012 19:29:28 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"01c35bc2fa3cd1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:47 GMT" + }, + { + "content-length": "954" + }, + { + "pragma": "no-cache" + }, + { + "cteonnt-length": "1996" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "x-radid": "P10723443-T100550693-C29000000000082620" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 170, + "wire": "88cddfd2e5ccd40f138ffe4627d913ae38ec8d364206e03f9fd07f088be393068dda78e800020037cf0f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001005" + }, + { + "date": "Sat, 03 Nov 2012 13:29:47 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 171, + "wire": "885894a8eb2127b0bf4a547588324e5fa52bb0ddc692ffe16496dc34fd2816d4d27eea08007940b97000b800298b46ff7f13e6acf4189eac2cb07f33a535dc61824952e392af285c87a58f0c918acf4189e98ad9ad7f34d1fcfd297b5c1fce9d5914bfbb5a97b56d521bfa14d7ba13a9af75f3a9ab86d3a9ba1d0753869da75356fda752ef0dca5ed5a14d30f152fe0d0a6edf0a9af6e0fe7f408cf2b794216aec3a4a4498f57f01300f28fe1d5d20cd2db03d2e271e56f79b3cff6047ff3fe951678bfd7957760e2c4f565d73b58767188b9ba7f8fc3a30b5738ff6d4fcd8785b3a70ff4b6df01da3fff2dc9ff9ff7c7f7ed4ac699e063ed42f9acd6151061b0df4a002b612c6b08016540b3704fdc69d53168dff6a487a466aa05c7255e50b90f4fda98d29af55547a5f92497ca589d34d1f6a1271d882a60b532acf7fd40f0d830b4e33", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store, no-cache, private" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 15 Nov 2008 16:00:00 GMT" + }, + { + "p3p": "policyref=\"http://cdn.adnxs.com/w3c/policy/p3p.xml\", CP=\"NOI DSP COR ADM PSAo PSDo OURo SAMo UNRo OTRo BUS COM NAV DEM STA PRE\"" + }, + { + "x-xss-protection": "0" + }, + { + "set-cookie": "anj=Kfu=8fG68%Cxrx)0s]#%2L_'x%SEV/hnJPh4FQV_eKj?9AMF4:V)4hY/82QjU'-Rw1Ra^uI$+VZ; path=/; expires=Fri, 01-Feb-2013 13:29:47 GMT; domain=.adnxs.com; HttpOnly" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "date": "Sat, 03 Nov 2012 13:29:47 GMT" + }, + { + "content-length": "1463" + } + ] + }, + { + "seqno": 172, + "wire": "88ec408aa924396a4ad416a9933f023432d9648872e09fb8d0948747d840874d8327535531a49a66391c7a3908b04af85668202ad1c8f8961bcdbe1648079f0b5f4089f2b567f05b0b22d1fa868776b5f4e0dfd8f40f0d023534", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache" + }, + { + "ntcoent-length": "42" + }, + { + "content-type": "image/gif" + }, + { + "expires": "6:29:42 AM" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "trackingid": "3bd68bdc-1e91-410e-bd92-a85913c08914" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "date": "Sat, 03 Nov 2012 13:29:47 GMT" + }, + { + "content-encoding": "gzip" + }, + { + "content-length": "54" + } + ] + }, + { + "seqno": 173, + "wire": "890f0d0130d8408721eaa8a4498f5788ea52d6b0e83772ffea6496d07abe940054ca3a940bef814002e001700053168dff58b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfdf", + "headers": [ + { + ":status": "204" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:29:47 GMT" + }, + { + "connection": "keep-alive" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 174, + "wire": "885f961d75d0620d263d4c7441eafb50938ec415305a99567be50f0d023335dcc1", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/json; charset=utf-8" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "35" + }, + { + "date": "Sat, 03 Nov 2012 13:29:47 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 175, + "wire": "88768c86b19272ad78fe8e92b015c37f0ac6acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5ee1b46a5fc1c46a6f8720d4d7ba11a9af75f1a9938c2353271be353570daa64d37d4e1a7229a61e3fcff588ba6d4256b0bdc741a41a4bf6496d07abe94036a693f7504008940b3704fdc69e53168df0f28ff1c949059da3c0ceb3db5b5aa5eef62f37f062c7941bc54eb2a5ced82c9fde58ff043fc061e3cdeb732de4c9e78cbdf469cdc5bedb75efd98f1eac365fbb83ce7f592cfd5ba67db7e2b19f04589b1dc3b7355937e6e7ef533ef9f16c524f99a93760b34beb60267d50a7b03ed4be7a466aa05d36d952e43d3f6a60f359ac2a20df3dbf4a004b681fa58400b2a059b827ee34f298b46ffb5358d33c0c75f95497ca58e83ee3412c3569fb24e3b1054c1c37e159e798624f6d5d4b27f5a839bd9abf9e3", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "cache-control": "must-revalidate" + }, + { + "expires": "Mon, 05 Nov 2012 13:29:48 GMT" + }, + { + "set-cookie": "fc=rqbE3Poup4Ofv8GxDEGHJ0T2mPet6qErhzJbX2aX0FVY8uK-xitYHevMNKV5qRPTQHHOFrDBExLyIrZ-jLRD_r3wc-cQ7FRKnITKYzO3zYV52dhK4dSErN9-EcLOAtq0; Domain=.turn.com; Expires=Thu, 02-May-2013 13:29:48 GMT; Path=/" + }, + { + "content-type": "text/javascript;charset=UTF-8" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "date": "Sat, 03 Nov 2012 13:29:47 GMT" + } + ] + }, + { + "seqno": 176, + "wire": "88c4c3f45f91497ca589d34d1f649c7620a98386fc2b3dbf58a0aec3771a4bf4a547588324e5fa52a3ac849ec2fd294da84ad617b8e83483497f6196dc34fd280654d27eea0801128166e09fb8d3ca62d1bf0f0d83682d0bcb7b8b84842d695b05443c86aa6f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "private, no-cache, no-store, must-revalidate" + }, + { + "date": "Sat, 03 Nov 2012 13:29:48 GMT" + }, + { + "content-length": "4142" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 177, + "wire": "887f0d842507417f58b5aec3771a4bf4a523f2b0e62c00fa52a3ac419272fd2951d6424f617e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692f6495dc34fd2815328ea50400014006e003700053168dff6c95dc34fd2815328ea50400014006e003700053168dff4085aec1cd48ff86a8eb10649cbf768986434beb716cee5b3ff10f0d0234350f28dd4401fa17cb607db091a27cb3b1aa0d8fce8f18be6cfdf45097c5fe75d5d64efbb2f5f65713ab4738bc4107cfda958d33c0c7da85f359ac2a20d07abe94032b693f758400b4a059b827ee34f298b46ffb5243d2335500e5f410af5153f77f0ed9acf4189eac2cb07f33a535dc6181c8b8e5f410af5152c5761bb8c9e97f34d1fcfd297b5c1fca9a756452feed6a69c97d486fe81a97f0711a9af7423535eebe353570daa6adfb46a64d37d4bdab429a61e2a6edf0a9ab7defe7", + "headers": [ + { + ":status": "200" + }, + { + "connection": "close" + }, + { + "cache-control": "private, max-age=0, no-cache, no-store, must-revalidate, proxy-revalidate" + }, + { + "expires": "Sat, 1 Jan 2000 01:01:00 GMT" + }, + { + "last-modified": "Sat, 1 Jan 2000 01:01:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "server": "AdifyServer" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "45" + }, + { + "set-cookie": "s=1,2*50951c4c*3Q4liHxMwG*rZye1ewDYpnkdvSJkze6tOMY_w==*; path=/; expires=Mon, 03-Nov-2014 13:29:48 GMT; domain=afy11.net;" + }, + { + "p3p": "policyref=\"http://ad.afy11.net/privacy.xml\", CP=\" NOI DSP NID ADMa DEVa PSAa PSDa OUR OTRa IND COM NAV STA OTC\"" + } + ] + }, + { + "seqno": 178, + "wire": "48826402ddc1dcdbda0f28bd41508803f6a5634cf031f6a17cd66b0a88375b57d280696d27eeb0801128166e09fb8d3ea62d1bfed490f48cd540b8e4abca1721e9fb531a535eaaa8f50f1fb29d29aee30c58ba6db2a5c87a58b188e4ff24909007e2b349036d7c13b96c803f169a481975b6dc68416d979c65a7df7df17f6196dc34fd280654d27eea0801128166e09fb8d3ea62d1bf0f0d01305f96497ca589d34d1f6a1271d882a60c9bb52cf3cdbeb07f", + "headers": [ + { + ":status": "302" + }, + { + "cache-control": "no-store, no-cache, private" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 15 Nov 2008 16:00:00 GMT" + }, + { + "p3p": "policyref=\"http://cdn.adnxs.com/w3c/policy/p3p.xml\", CP=\"NOI DSP COR ADM PSAo PSDo OURo SAMo UNRo OTRo BUS COM NAV DEM STA PRE\"" + }, + { + "x-xss-protection": "0" + }, + { + "set-cookie": "sess=1; path=/; expires=Sun, 04-Nov-2012 13:29:49 GMT; domain=.adnxs.com; HttpOnly" + }, + { + "location": "http://r.turn.com/r/bd?ddc=1&pid=54&cver=1&uid=3755642153863499992" + }, + { + "date": "Sat, 03 Nov 2012 13:29:49 GMT" + }, + { + "content-length": "0" + }, + { + "content-type": "text/html; charset=ISO-8859-1" + } + ] + }, + { + "seqno": 179, + "wire": "887689c540d08c2644ea77677f03c6acf4189eac2cb07f2c473b1e192315b35afe69a3f9fa52f6b83f9d3ab2297f76b52f6adaa69c97d4bdc368d4bf8388d4d7ba11a9ab86d52ef0dca5ed5a14d30f153269dffcff0f28deae38ac4c7117bc0259b65b69c0aec85f699036e32d81b081f0b6ebeb81702e165b0bed3ed85a71a77ed4be7a466aa05c87a925f29f058d721e9fb53079acd615106eb6afa500cada4fdd61002ca8166e321b8db4a62d1bfed4d634cf031f40872785905b3b96cf87a261ac3aeb7002589caec3771a4bf4a523f2b0e62c00fa52a3ac419272fd2951d6424f617f64022d315f95352398ac4c697ec938ec4153064dda9679e6df583f5b842d4b70ddd46196dc34fd280654d27eea0801128166e321b8db4a62d1bf", + "headers": [ + { + ":status": "200" + }, + { + "server": "GlassFish v3" + }, + { + "p3p": "policyref=\"/bh/w3c/p3p.xml\", CP=\"NOI DSP COR NID CURa DEVa PSAa OUR BUS COM NAV INT\"" + }, + { + "set-cookie": "pb_rtb_ev=2-535461.3194305635051091579.0.0.1351949514647; Domain=.contextweb.com; Expires=Sun, 03-Nov-2013 13:31:54 GMT; Path=/" + }, + { + "cw-server": "lga-app602" + }, + { + "cache-control": "private, max-age=0, no-cache, no-store" + }, + { + "expires": "-1" + }, + { + "content-type": "image/gif;charset=ISO-8859-1" + }, + { + "content-language": "en-US" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 13:31:54 GMT" + } + ] + }, + { + "seqno": 180, + "wire": "c85892ace84ac49ca4eb003e94aec2ac49ca4eb0035d89ac7626a2d8bce9a68f5f87497ca589d34d1fca6496d07abe94138a65b68502fbeea806ee001700053168df6c96dc34fd280654d27eea0801128166e09fb8d3ea62d1bf0f1fa89d29aee30c124a9745674f924e3aa62ae43d2c52590c36133db4c6862b3792d0c566f25a1798d2ff7f0aabbdae0fe74eac8a5fddad4bdab6a9af7427535eebe753570daa64d37d4e1a72297b568534c3c5486fe81ff3d176ad86b19272b025c4b882a7f5c2a379fed4a4f2448450c09712e20a9aab2d5bb767600bbebbc55a535685ac9cb4370f28ff6cac7626a2d8b0202e9ad3ef40334dd61e1b35610f7e1de6f7eef61068cbe3ad78adc2cf2f58bf095ddcdabf73f3cf95515d545876e54e2f2eeac7e88ecbe78c9aaea383546ef2697f4d7b67a19b2bdde19ddc03cfa6ad38bf42d3a1b346a1e2ad9929efbcf4f0aedd4d9c9ca9ebe6ec2d766eacc8e5fb297f5cd79ff7ca3c19ec5e2484d34ecc9abc61bafef95515d94b18386b14b313bd1b70ecdf9756c85cb43e32ce0b1ad5b19ced2a2bacfe63708eeaeda1566ffda85f359ac2a20dd6d5f4a0195b40ec58400b2a059b827ee34fa98b46ffb52b1a67818fb5243d2335502e8ace9f249c754c55c87a7f4082492a8424e7310b7b86a8b31d261a4bdee7", + "headers": [ + { + ":status": "302" + }, + { + "cache-control": "post-check=0, pre-check=0" + }, + { + "content-location": "partner.html" + }, + { + "content-type": "text/html" + }, + { + "date": "Sat, 03 Nov 2012 13:29:49 GMT" + }, + { + "expires": "Mon, 26 Jul 1997 05:00:00 GMT" + }, + { + "last-modified": "Sat, 03 Nov 2012 13:29:49 GMT" + }, + { + "location": "http://cdn.spotxchange.com/media/thumbs/pixel/pixel.gif" + }, + { + "p3p": "CP=\"NOI DSP COR PSAo PSDo OUR IND UNI COM NAV ADMa\"" + }, + { + "pragma": "no-cache" + }, + { + "server": "Apache/2.2.21 (Unix) mod_ssl/2.2.21 OpenSSL/0.9.8e-fips-rhel5" + }, + { + "set-cookie": "partner-0=eNptzM0KgkAUQOF1vUvgzzCF0MJwkpGuF3WyGXcpBKOZLYLJ%2B%2FRJtGx7OHyc7fxVdOBsU4lSxifZiCQyaiJ8vAh7EaLNnNGZ1471rMOaGp3dmvTomUpuO5ocWmkxBA4q5nKsWZfeZ6PLZxswi8GwdAigh3dOwFB9Tf%2Bfeb0UP2fgcvlRFQTJOQA6u1wJh0r4OQ3L4%2B3XH6c7OqM%3D; expires=Sun, 03-Mar-2013 13:29:49 GMT; path=/; domain=.spotxchange.com" + }, + { + "tcn": "choice" + }, + { + "vary": "negotiate" + }, + { + "transfer-encoding": "chunked" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 181, + "wire": "d10f1fca9d29aee30c58ba6db2a5c87a58b18252860d2300624908c058acd2301798b4d231fe4c73cd416298d2417a1c1bb064dfb594e7c14649bce9409be9ef8bda2407c4c73cd41622772d9007d0d4f3f85f92497ca589d34d1f6a1271d882a60e1bf0acf7768abc73f53154d0349272d90f0d8264007f308a0fda949e42c11d07275f", + "headers": [ + { + ":status": "302" + }, + { + "location": "http://r.turn.com/r/cms/id/0/ddc/1/pid/18/uid/?google_gid=CAESEITR3tLElIgxNs25jzV8Md0&google_cver=1" + }, + { + "date": "Sat, 03 Nov 2012 13:29:49 GMT" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "text/html; charset=UTF-8" + }, + { + "server": "Cookie Matcher" + }, + { + "content-length": "300" + }, + { + "x-xss-protection": "1; mode=block" + } + ] + }, + { + "seqno": 182, + "wire": "88d3768586b19272ff589baed8e8313e94a47e561cc581907d295d87f3e96b0bdc741a41a4bfc8d97f0799bdae0fe6f70daa437f429ab86d534eadaa6edf0a9a725ffe7f0f28cc34048e42362906b46cc8d2cd4aebeb464740b3211064001c964947f6a17cd66b0a88341eafa500cada4fdd61002d28166e09fb8d3ea62d1bfed4ac699e063ed490f48cd540b9eb2d5e57a8a90f0d023433de5f87352398ac4c697f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:29:49 GMT" + }, + { + "server": "Apache" + }, + { + "cache-control": "public, max-age=30, proxy-revalidate" + }, + { + "expires": "Mon, 26 Jul 1997 05:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "p3p": "CP=\"CUR ADM OUR NOR STA NID\"" + }, + { + "set-cookie": "i=cbdc52da-b3d4-4f79-bc70-3121d006fdfa; expires=Mon, 03-Nov-2014 13:29:49 GMT; path=/; domain=.openx.net" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 183, + "wire": "88d7769186b19272b025c4bb2a7f578b52756efeff7f3188d78f5b0daecaecff7f02afbdae0fe74eac8a5ee1b46a437f40d4bf8388d4df0e41a9ab86d52ef0dca64d37d4e1a72297b568534c3c54c9a77ff30f28cbaed4c410bcdc0c85f699036e32d81b081f0b6ebff6a17cd66b0a8839164fa50025b28ea58400b2a059b827ee34fa98b46ffb52b1a67818fb5243d2335502f65b19887aabb0fd0a44ae43d30f0d0234394088ea52d6b0e83772ff8f49a929ed4c0c83e94a47e607df03bf7f2488cc52d6b4341bb97fc3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:29:49 GMT" + }, + { + "server": "Apache/2.2.3 (CentOS)" + }, + { + "x-powered-by": "PHP/5.3.3" + }, + { + "p3p": "CP=\"NOI CURa ADMa DEVa TAIa OUR BUS IND UNI COM NAV INT\"" + }, + { + "set-cookie": "put_1185=3194305635051091579; expires=Wed, 02-Jan-2013 13:29:49 GMT; path=/; domain=.rubiconproject.com" + }, + { + "content-length": "49" + }, + { + "keep-alive": "timeout=30, max=9907" + }, + { + "connection": "Keep-Alive" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 184, + "wire": "88dc769186b19272b025c4b884a7f5c23b6a4dbfdf7f0390d78f5b0daecae102c1b63b6a4dacaed7e258acaec3771a4bf4a523f2b0e62c00fa52a3ac419272fd2948fcac398b03ce34007d294da84ad617b8e83483497fc70f28dfa3a26c4c70172fab38f4cbc11464f5a6cd80d1bf9f8d3bf40b4ef843a73c20d37f933c731f0c381fef74932acdffb50be6b3585441badabe94032b693f758400b2a059b827ee34fa98b46ffb52b1a67818fb5243d2335502f41ba192b90f4f0f0d0234336496dd6d5f4a01a5349fba820044a059b827ee34fa98b46fe8c7", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:29:49 GMT" + }, + { + "server": "Apache/2.2.22 (Ubuntu)" + }, + { + "x-powered-by": "PHP/5.3.10-1ubuntu3.4" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "private, max-age=0, no-cache, max-age=86400, must-revalidate" + }, + { + "p3p": "CP=\"CUR ADM OUR NOR STA NID\"" + }, + { + "set-cookie": "ljtrtb=eJyrVjJUslIyNrQ0MTYwNTM2NTA1NLA0NDW3VKoFAE9vBcg%3D; expires=Sun, 03-Nov-2013 13:29:49 GMT; path=/; domain=.lijit.com" + }, + { + "content-length": "43" + }, + { + "expires": "Sun, 04 Nov 2012 13:29:49 GMT" + }, + { + "connection": "close" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 185, + "wire": "88f3f258b1a47e561cc5801f4a547588324e5fa52a3ac849ec2fd295d86ee3497e94a6d4256b0bdc741a41a4bf4a216a47e47316007fe5c80f0d023433eb", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:29:48 GMT" + } + ] + }, + { + "seqno": 186, + "wire": "88f4f3bee50f28c2b4d240c85f699036e32d81b081f0b6ebff6a5f3d2335502e9b6ca9721e9fb53079acd615106f9edfa50025b40fd2c20059502cdc13f71a7d4c5a37fda9ac699e063fc80f0d023433e1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "set-cookie": "uid=3194305635051091579; Domain=.turn.com; Expires=Thu, 02-May-2013 13:29:49 GMT; Path=/" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:29:49 GMT" + } + ] + }, + { + "seqno": 187, + "wire": "88f4f3bee50f28c2b4d240c85f699036e32d81b081f0b6ebff6a5f3d2335502e9b6ca9721e9fb53079acd615106f9edfa50025b40fd2c20059502cdc13f71a7d4c5a37fda9ac699e063fc80f0d023433e1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "set-cookie": "uid=3194305635051091579; Domain=.turn.com; Expires=Thu, 02-May-2013 13:29:49 GMT; Path=/" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:29:49 GMT" + } + ] + }, + { + "seqno": 188, + "wire": "88f4f3bee50f28c2b4d240c85f699036e32d81b081f0b6ebff6a5f3d2335502e9b6ca9721e9fb53079acd615106f9edfa50025b40fd2c20059502cdc13f71a7d4c5a37fda9ac699e063fc80f0d023433e1", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "set-cookie": "uid=3194305635051091579; Domain=.turn.com; Expires=Thu, 02-May-2013 13:29:49 GMT; Path=/" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:29:49 GMT" + } + ] + }, + { + "seqno": 189, + "wire": "8876871c83ad3dd80ae0c9c40f28ff01b131df1a46083f9ea5f5026db2ab9dc745a58190bed3206dc65b036103e16dd7ee17cd66b0a885306e1a7fde93f7ff6107fb036ab3089f55985a7ffdebddbffd880115c644b5e3d358d268e82c09b2d2ff3f7ac699e063eef9e919aa8171c83ad74f7fbc1e6b3585441a0f57d280656d27eeb08016940b3704fdc69f53168dff0f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "server": "adaptv/1.0" + }, + { + "content-type": "image/gif" + }, + { + "connection": "Keep-Alive" + }, + { + "set-cookie": "rtbData0=\"key=turn:value=3194305635051091579:expiresAt=Sat+Nov+10+05%3A29%3A49+PST+2012:32-Compatible=true\";Path=/;Domain=.adap.tv;Expires=Mon, 03-Nov-2014 13:29:49 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 190, + "wire": "88f552848fd24a8f0f1392e4c7f220bed3ab0596c2f01e64410001fcff6c96df3dbf4a002a693f75040089410ae05eb8d054c5a37f5f88352398ac74acb37f0f0d84105f69dfef7f0888ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "W/\"21947-1351808321000\"" + }, + { + "last-modified": "Thu, 01 Nov 2012 22:18:41 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "content-length": "21947" + }, + { + "date": "Sat, 03 Nov 2012 13:29:48 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 191, + "wire": "88e676b686b19272b025c4bb4a7f5c2a379fed4bf0f1604a5279224228604b897694d5596addbb3b005df5dd1a949e48a51a12498cc09769717f0f28c6d7c2eedc1be1db8b06f81e144169a71b6dd65e7fed490f48cd5415db1d234988b90f4fda85f359ac2a20df697e94032b693f758400b6a059b827ee34fa98b46ffb52b1a678180f0d01317f0ce2bdae0fe74eac8a5fddad4bdab6a99e1e4a5ee1b5486fe83a97f0713a9be1c87535ee84ea6bdd7cea64e309d4c9c6f9d4c79371d4d5bf59d4d5c36a9ba1d0752ef0dca70d3914bdab429a61e2a64d3bd4bf834297b4ef5376f854d7b70299f55efe7f5894a8eb2127b0bf4a547588324e5fa52bb0ddc692ffedf1dd", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:29:49 GMT" + }, + { + "server": "Apache/2.2.4 (Unix) DAV/2 mod_ssl/2.2.4 OpenSSL/0.9.7a mod_fastcgi/2.4.2" + }, + { + "set-cookie": "PUBRETARGET=82_1446557389; domain=pubmatic.com; expires=Tue, 03-Nov-2015 13:29:49 GMT; path=/" + }, + { + "content-length": "1" + }, + { + "p3p": "CP=\"NOI DSP COR LAW CUR ADMo DEVo TAIo PSAo PSDo IVAo IVDo HISo OTPo OUR SAMo BUS UNI COM NAV INT DEM CNT STA PRE LOC\"" + }, + { + "cache-control": "no-store, no-cache, private" + }, + { + "pragma": "no-cache" + }, + { + "connection": "close" + }, + { + "content-type": "text/html" + } + ] + }, + { + "seqno": 192, + "wire": "88d36c97df3dbf4a09c5340fd2820042a05bb8dbf719714e1bef7f0f0d023433d1588aa47e561cc5802f0996dd6196dc34fd280654d27eea0801128166e09fb8d814c5a37fc4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "last-modified": "Thu, 26 May 2011 15:59:36 UTC" + }, + { + "content-length": "43" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "max-age=182357" + }, + { + "date": "Sat, 03 Nov 2012 13:29:50 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 193, + "wire": "88d67f03b5acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5fddad4bdab6a97f0711a9be1c8353570daa5de1b94e1a727f3d46496dc34fd280654d27eea0801128166e09fb8d814c5a37f5895a47e561cc5801f4a547588324e5fa52a3ac849ec2ff3c10f0d023433c70f28b6bda2fdf83ee43d23355010681d05a4b2186b90f4fdd634cf031f65f359ac2a20dd6d5f4a01a5349fba820044a059b827ee36053168df", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR DEVa TAIa OUR BUS UNI\"" + }, + { + "content-type": "image/gif" + }, + { + "expires": "Sat, 03 Nov 2012 13:29:50 GMT" + }, + { + "cache-control": "max-age=0, no-cache, no-store" + }, + { + "pragma": "no-cache" + }, + { + "date": "Sat, 03 Nov 2012 13:29:50 GMT" + }, + { + "content-length": "43" + }, + { + "connection": "keep-alive" + }, + { + "set-cookie": "CMDD=;domain=casalemedia.com;path=/;expires=Sun, 04 Nov 2012 13:29:50 GMT" + } + ] + }, + { + "seqno": 194, + "wire": "88d97f01c7acf4189eac2cb07f33a535dc61848e65c72525a245c87a58f0c918ad9ad7f34d1fcfd297b5c1fcebdd09d4d7baf9d4d5c36a9ba1d0a6adfb54bbc37297f76b521cf9d4bdab6ff3f45886a8eb2127b0bfe40f0d023335d8c3c9", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache" + }, + { + "p3p": "policyref=\"http://tag.admeld.com/w3c/p3p.xml\", CP=\"PSAo PSDo OUR SAM OTR BUS DSP ALL COR\"" + }, + { + "pragma": "no-cache" + }, + { + "cache-control": "no-store" + }, + { + "expires": "Mon, 26 Jul 1997 05:00:00 GMT" + }, + { + "content-length": "35" + }, + { + "content-type": "image/gif" + }, + { + "date": "Sat, 03 Nov 2012 13:29:50 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 195, + "wire": "885a839bd9ab5283a8f5175899a8eb10649cbf4a54759093d85fa529b5095ac2f71d0690692fdbc6e77f03cbacf4189eac2cb07f33a535dc61894d4150b8e48ec324ab90f4b1e192315b35afe69a3f9fabdae0fe74eac8a6bdd0a9af75f53570daa64d37d4e1a7229a61e2a5fc1a14ddbe15356fbdfcff7688fcd7831c6c05717f0f28e2b2314178db335de84068e9cdbd3e7a79f2989cefe301b07bd1e756fd9ef45fe02d1ef878d3bf078d5bf0074fbebb21d9f6a5634cf031f6a487a466aa05c7247619255c87a7ed42f9acd6151061b0df4a002b612c6b08016540b3704fdc6c0a62d1bf0f0d023539", + "headers": [ + { + ":status": "200" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "none" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "content-type": "image/gif" + }, + { + "date": "Sat, 03 Nov 2012 13:29:50 GMT" + }, + { + "expires": "Mon, 26 Jul 1997 05:00:00 GMT" + }, + { + "p3p": "policyref=\"http://files.adbrite.com/w3c/p3p.xml\",CP=\"NOI PSA PSD OUR IND UNI NAV DEM STA OTC\"" + }, + { + "server": "XPEHb/1.2" + }, + { + "set-cookie": "rb2=CiQKBjc0MjY5Nxjxxt_6vwEiEzMxOTQzMDU2MzUwNTEwOTE1NzkQAQ; path=/; domain=.adbrite.com; expires=Fri, 01-Feb-2013 13:29:50 GMT" + }, + { + "content-length": "59" + } + ] + }, + { + "seqno": 196, + "wire": "88c2c1c0ddc8e9bfbe0f28e3b2314178dc335df783ce8dfa1ad3ef67375e8e55ac7aee49f47bd1bfa8347b843a7a680e8bfc3ce8bfd7ce9de46f04383ed4ac699e063ed490f48cd540b8e48ec324ab90f4fda85f359ac2a20c361be940056c258d61002ca8166e09fb8d814c5a37ff0f0d023539", + "headers": [ + { + ":status": "200" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "none" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "content-type": "image/gif" + }, + { + "date": "Sat, 03 Nov 2012 13:29:50 GMT" + }, + { + "expires": "Mon, 26 Jul 1997 05:00:00 GMT" + }, + { + "p3p": "policyref=\"http://files.adbrite.com/w3c/p3p.xml\",CP=\"NOI PSA PSD OUR IND UNI NAV DEM STA OTC\"" + }, + { + "server": "XPEHb/1.2" + }, + { + "set-cookie": "rb2=CiUKBzExMTM4NzQY78bf-r8BIhMzMTk0MzA1NjM1MDUxMDkxNTc5EAE; path=/; domain=.adbrite.com; expires=Fri, 01-Feb-2013 13:29:50 GMT" + }, + { + "content-length": "59" + } + ] + }, + { + "seqno": 197, + "wire": "88588da8eb10649cbf4a54759093d85f4085aec1cd48ff86a8eb10649cbf5f92497ca589d34d1f6a1271d882a60b532acf7fc5f37b8b84842d695b05443c86aa6f768dd06258741e54ad9326e61e5c1f408ef2b0d15d454d3dc8b772d8831eaf03342e30408bf2b5a35887a6b1a4d1d05f8cc9820c124c5fb24f61e92c014090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb408bf2b4b60e92ac7ad263d48f89dd0e8c1ab6e4c5934f408cf2b0d15d454addcb620c7abf8769702ec8190bff7f21868776b5f4e0dfc16196dc34fd280654d27eea0801128166e09fb8d854c5a37f0f0d84134c89ef6c96e4593e94642a6a225410022504cdc0b971b714c5a37fde0f138ffe5f6c2eb619051c91ba4903701fcf", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "-1" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-aspnetmvc-version": "4.0" + }, + { + "x-ua-compatible": "IE=Edge;chrome=1" + }, + { + "x-content-type-options": "nosniff" + }, + { + "x-frame-options": "SAMEORIGIN" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "date": "Sat, 03 Nov 2012 13:29:51 GMT" + }, + { + "content-length": "24328" + }, + { + "last-modified": "Wed, 31 Oct 2012 23:16:56 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"951751d2bdb7cd1:0\"" + } + ] + }, + { + "seqno": 198, + "wire": "88588ca47e561cc58190b6cb80003f5f86497ca582211fd1e00f138dfe5e03e40bcf371889206e03f9c9c8c2c50f0d8375d1335585742eb2e35f6196dc34fd280654d27eea0801128166e09fb8d894c5a37f6c96e4593e94134a6a225410022502e5c65bb807d4c5a37f6496dc34fd282714d444a820059500e5c0b371a794c5a37fe1", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "text/css" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"809c1885b2cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "7723" + }, + { + "age": "717364" + }, + { + "date": "Sat, 03 Nov 2012 13:29:52 GMT" + }, + { + "last-modified": "Wed, 24 Oct 2012 16:35:09 GMT" + }, + { + "expires": "Sat, 26 Oct 2013 06:13:48 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 199, + "wire": "88c35f901d75d0620d263d4c741f71a0961ab4ffd6e50f138efe40f32d32cb4e4622481b80fe7fcecdc7ca0f0d84085a7dbf5585742eb2e33fc26c96e4593e94134a6a225410022502e5c65fb8dbca62d1bf6496dc34fd282714d444a820059500e5c0b371a7d4c5a37fe5", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "application/javascript" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"08343346b2cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "11495" + }, + { + "age": "717363" + }, + { + "date": "Sat, 03 Nov 2012 13:29:52 GMT" + }, + { + "last-modified": "Wed, 24 Oct 2012 16:39:58 GMT" + }, + { + "expires": "Sat, 26 Oct 2013 06:13:49 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 200, + "wire": "88d7d3f464022d316c96d07abe940bca65b68504008540bf700cdc65d53168dfea0f138ffe4627d913ae38ec8d364206e03f9f768dd06258741e54ad9326e61d5dbf4001738be393068dda78e800020033cd0f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001003" + }, + { + "date": "Sat, 03 Nov 2012 13:29:51 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 201, + "wire": "88cb5f87352398ac5754dfdeed0f138dfe5a8e3215f0b91889206e03f9d6d5cfd20f0d84081f705fc5c96c96e4593e94134a6a225410022502e5c65eb8cb2a62d1bfc4eb", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/png" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"4bbce916b2cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "10962" + }, + { + "age": "717363" + }, + { + "date": "Sat, 03 Nov 2012 13:29:52 GMT" + }, + { + "last-modified": "Wed, 24 Oct 2012 16:38:33 GMT" + }, + { + "expires": "Sat, 26 Oct 2013 06:13:49 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 202, + "wire": "8858a1aec3771a4bf4a547588324e5fa52bb0fe7d2d617b8e83483497e94a8eb2127b0bfda5f87352398ac4c697f6c96df3dbf4a080a6a2254100215020b8276e36f298b46fff10f138efe40d4631965089e94840dc07f3fc4d37f20a9bdae0fe6ef0dca5ee1b54bdab49d4c3934a9938df3a9ab4e753570daa6bc7cd4dd0e83a9bf0673ff3f0f28b9874ead37b1e6801f6a487a466aa022f4a2a5c87a7ed42f9acd615106fb4bf4a01c5b49fbac20044a059b827ee32053168dff6a5634cf031f7fce0f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-cache, proxy-revalidate, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 20 Oct 2011 10:27:58 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"04baaef128fcc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "set-cookie": "ANONCHK=0; domain=c.msn.com; expires=Tue, 06-Nov-2012 13:29:30 GMT; path=/;" + }, + { + "date": "Sat, 03 Nov 2012 13:29:52 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 203, + "wire": "88d1c3e3f20f138efe492b2592591d6e311240dc07f3dbdad4d70f0d831000d7cfce6c96e4593e94134a6a225410022502e5c65db821298b46ffcdf0", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "max-age=31536000" + }, + { + "content-type": "image/png" + }, + { + "content-encoding": "gzip" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"cf3edfd75b2cd1:0\"" + }, + { + "vary": "Accept-Encoding" + }, + { + "server": "Microsoft-IIS/8.0" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "2004" + }, + { + "age": "717364" + }, + { + "date": "Sat, 03 Nov 2012 13:29:52 GMT" + }, + { + "last-modified": "Wed, 24 Oct 2012 16:37:22 GMT" + }, + { + "expires": "Sat, 26 Oct 2013 06:13:48 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 204, + "wire": "885892a8eb10649cbf4a536a12b585ee3a0d20d25f5f92497ca589d34d1f6a5e9c7620a982d4cab3df6c96c361be94036a6a225410022502fdc13f704f298b46fff60f138efe401232dc6414a3649206e03f9fc954012ac3d30f0d03393535e2408a224a7aaa4ad416a9933f831000f76496c361be940054ca3a940bef814002e001700053168dff4086f2b58390d27f9bd6103a265a6995b784006db038fb2b5e13e0000000000003c27040eb", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "text/html; Charset=utf-8" + }, + { + "last-modified": "Fri, 05 Oct 2012 19:29:28 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"01c35bc2fa3cd1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:52 GMT" + }, + { + "content-length": "955" + }, + { + "pragma": "no-cache" + }, + { + "cteonnt-length": "2008" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "x-radid": "P10723443-T100550693-C29000000000082620" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 205, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b03207dc75eff9768abda83a35ebddbef42077dfdec87f0f8abda83a35ebddbef420770f0d84138cb8ff5584704c805fda6496df697e94038a693f750400894082e04571a794c5a37f408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=309678" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "26369" + }, + { + "age": "62302" + }, + { + "date": "Sat, 03 Nov 2012 13:29:52 GMT" + }, + { + "expires": "Tue, 06 Nov 2012 10:12:48 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 206, + "wire": "88efebced5d452848fd24a8f0f138ffe4627d913ae38ec8d364206e03f9fd47f038be393068dda78e800020035e30f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001004" + }, + { + "date": "Sat, 03 Nov 2012 13:29:51 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 207, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c81c10bf5f88352398ac74acb37f768abda83a35ebddbef4207be8e7d17f028abda83a35ebddbef4207b0f0d8469b65f6bc6e26496e4593e9403aa693f7504008940bf71a7ae32253168dfc5", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=430622" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "45394" + }, + { + "age": "62302" + }, + { + "date": "Sat, 03 Nov 2012 13:29:52 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 19:48:32 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 208, + "wire": "885894a8eb2127b0bf4a547588324e5fa52bb0ddc692fff36496dc34fd2816d4d27eea08007940b97000b800298b46ff7f16e6acf4189eac2cb07f33a535dc61824952e392af285c87a58f0c918acf4189e98ad9ad7f34d1fcfd297b5c1fce9d5914bfbb5a97b56d521bfa14d7ba13a9af75f3a9ab86d3a9ba1d0753869da75356fda752ef0dca5ed5a14d30f152fe0d0a6edf0a9af6e0fe7f408cf2b794216aec3a4a4498f57f01300f28ff101d5d20cd2db03d2e26ffdfff97bcd9e7fb023ff9ff4a8b3c5febcabbb071627ab37ff02d61d9c622e6e9fe3f0e8c2d5ce3fdb53f361e16ce9c3fd2db7c07afff9caf8bfebff260ff65b337f1fc7cd3fe6e83fda3bf6fb52b1a67818fb50be6b358544186c37d2800ad84b1ac20059502cdc13f71b1298b46ffb5243d2335502e392af285c87a7ed4c694d7aaaa3d7ff5e70f0d830b2f87", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store, no-cache, private" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 15 Nov 2008 16:00:00 GMT" + }, + { + "p3p": "policyref=\"http://cdn.adnxs.com/w3c/policy/p3p.xml\", CP=\"NOI DSP COR ADM PSAo PSDo OURo SAMo UNRo OTRo BUS COM NAV DEM STA PRE\"" + }, + { + "x-xss-protection": "0" + }, + { + "set-cookie": "anj=Kfu=8fG5+^Cxrx)0s]#%2L_'x%SEV/hnK]14FQV_eKj?9AMF4:V)4hY/82QjU'-Rw1k^WD2#$i1)erK!!*m?S=+svq; path=/; expires=Fri, 01-Feb-2013 13:29:52 GMT; domain=.adnxs.com; HttpOnly" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "date": "Sat, 03 Nov 2012 13:29:52 GMT" + }, + { + "content-length": "1391" + } + ] + }, + { + "seqno": 209, + "wire": "88768c86b19272ad78fe8e92b015c37f01c6acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5ee1b46a5fc1c46a6f8720d4d7ba11a9af75f1a9938c2353271be353570daa64d37d4e1a7229a61e3fcff588ba6d4256b0bdc741a41a4bf6496d07abe94036a693f7504008940b3704fdc6c4a62d1bf0f28ff1b94906bf185bc5961b1fdf8f539807c7afaeae55438814d0f1d9a0b740ffa2d096f4a1a0f37adccb793279e32f7d1a73716fb6dd7bdc262f3beaba2cd97a7ac58bb7ef17f5bafbacf822c4d8ee1db9aac9bf373f7a99f7cf8b62927ccd49bb059a5f5b0133ea853d81f6a5f3d2335502e9b6ca9721e9fb53079acd615106f9edfa50025b40fd2c20059502cdc13f71b1298b46ffb5358d33c0c7f5f95497ca58e83ee3412c3569fb24e3b1054c1c37e159e798624f6d5d4b27f5a839bd9abfbee", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "cache-control": "must-revalidate" + }, + { + "expires": "Mon, 05 Nov 2012 13:29:52 GMT" + }, + { + "set-cookie": "fc=PwF5GJAr9THO6EaVkyk6nl6s2gAVQMeB09yelt5Ns41Y8uK-xitYHevMNKV5qRPT6cGxTnB2KJjyGGqZV9P7973wc-cQ7FRKnITKYzO3zYV52dhK4dSErN9-EcLOAtq0; Domain=.turn.com; Expires=Thu, 02-May-2013 13:29:52 GMT; Path=/" + }, + { + "content-type": "text/javascript;charset=UTF-8" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "date": "Sat, 03 Nov 2012 13:29:52 GMT" + } + ] + }, + { + "seqno": 210, + "wire": "488264027f05afbdae0fe74eac8a5ee1b46a437f40d4bf8388d4df0e41a9ab86d52ef0dca64d37d4e1a72297b568534c3c54c9a77ff36496df3dbf4a002a651d4a05f740a0017000b800298b46ff0f28a0ae00ad26ba75eb6dbcad4a0ddf7ac699e063eef9e919aa817b2534f6c6b90f4f0f1fc79d29aee30c1a35c7255e50b90f4b15f9e9fe4669242d9005ef8416681975e7001f8191263d5020a9b4d223faff4e3a2744d85f6c4d3227df71a7ffd7d7faff5fdfdfc58590d6410f0d0130", + "headers": [ + { + ":status": "302" + }, + { + "p3p": "CP=\"NOI CURa ADMa DEVa TAIa OUR BUS IND UNI COM NAV INT\"" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "set-cookie": "p=1-dPmPP55J4f0S;Path=/;Domain=.rfihub.com" + }, + { + "location": "http://ib.adnxs.com/pxj?bidder=18&seg=378601&action=setuids('672725195243299649','');&redir=" + }, + { + "content-length": "0" + } + ] + }, + { + "seqno": 211, + "wire": "88cb4085aec1cd48ff86a8eb10649cbfcbcac90f28ff111d5d20cd2db03d2e276fe3bde6cf3fd811ffcffa5459e2ff5e55dd838b13d59bfbf2d61d9c622e6e9fe3f0e8c2d5ce3fdb53f361e16ce9c3fd2db7c07afff9caf8bfebff260ff65b3438ee6d7ecbfc7fa260fda6e9b6f3fb52b1a67818fb50be6b358544186c37d2800ad84b1ac20059502cdc13f71b1298b46ffb5243d2335502e392af285c87a7ed4c694d7aaaa3d70f0d023433e4f2", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store, no-cache, private" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 15 Nov 2008 16:00:00 GMT" + }, + { + "p3p": "policyref=\"http://cdn.adnxs.com/w3c/policy/p3p.xml\", CP=\"NOI DSP COR ADM PSAo PSDo OURo SAMo UNRo OTRo BUS COM NAV DEM STA PRE\"" + }, + { + "x-xss-protection": "0" + }, + { + "set-cookie": "anj=Kfu=8fG7DHCxrx)0s]#%2L_'x%SEV/hnK)x4FQV_eKj?9AMF4:V)4hY/82QjU'-Rw1k^WD2#$i1)erM67KPze!'cEZmBiRY; path=/; expires=Fri, 01-Feb-2013 13:29:52 GMT; domain=.adnxs.com; HttpOnly" + }, + { + "content-length": "43" + }, + { + "content-type": "image/gif" + }, + { + "date": "Sat, 03 Nov 2012 13:29:52 GMT" + } + ] + }, + { + "seqno": 212, + "wire": "890f0d0130f2d4be6496d07abe940054ca3a940bef814002e001700053168dff58b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfe6", + "headers": [ + { + ":status": "204" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:29:52 GMT" + }, + { + "connection": "keep-alive" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 213, + "wire": "885f961d75d0620d263d4c7441eafb50938ec415305a99567b4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb0f0d023335f6d8", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "application/json; charset=utf-8" + }, + { + "x-content-type-options": "nosniff" + }, + { + "content-length": "35" + }, + { + "date": "Sat, 03 Nov 2012 13:29:52 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 214, + "wire": "88cccbc25f91497ca589d34d1f649c7620a98386fc2b3dc758a0aec3771a4bf4a547588324e5fa52a3ac849ec2fd294da84ad617b8e83483497ff80f0d83682d0bda7b8b84842d695b05443c86aa6f", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "private, no-cache, no-store, must-revalidate" + }, + { + "date": "Sat, 03 Nov 2012 13:29:52 GMT" + }, + { + "content-length": "4142" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 215, + "wire": "c8cfce58b1a47e561cc5801f4a547588324e5fa52a3ac849ec2fd295d86ee3497e94a6d4256b0bdc741a41a4bf4a216a47e47316007fc60f28c2b4d240c85f699036e32d81b081f0b6ebff6a5f3d2335502e9b6ca9721e9fb53079acd615106f9edfa50025b40fd2c20059502cdc13f71b654c5a37fda9ac699e063fec0f0d0234336196dc34fd280654d27eea0801128166e09fb8db2a62d1bf0f1fad9d29aee30c495d2bc85a642f95ea2a583468b9256692065d6fe24aedb4d240c85f699036e32d81b081f0b6ebffcc", + "headers": [ + { + ":status": "302" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "set-cookie": "uid=3194305635051091579; Domain=.turn.com; Expires=Thu, 02-May-2013 13:29:53 GMT; Path=/" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:29:53 GMT" + }, + { + "location": "http://dpm.demdex.net/ibs:dpid=375&dpuuid=3194305635051091579" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 216, + "wire": "cad1d0bfc70f28c2b4d240c85f699036e32d81b081f0b6ebff6a5f3d2335502e9b6ca9721e9fb53079acd615106f9edfa50025b40fd2c20059502cdc13f71b654c5a37fda9ac699e063fed0f0d023433be0f1fa99d29aee30c24732178e8b4bd4665c87a584192561a69f7ffc34903217da640db8cb606c207c2dbafffcc", + "headers": [ + { + ":status": "302" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "set-cookie": "uid=3194305635051091579; Domain=.turn.com; Expires=Thu, 02-May-2013 13:29:53 GMT; Path=/" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:29:53 GMT" + }, + { + "location": "http://tags.bluekai.com/site/4499?id=3194305635051091579" + }, + { + "transfer-encoding": "chunked" + } + ] + }, + { + "seqno": 217, + "wire": "88d1d0bfc70f28c2b4d240cb6f09f7c2db6f32ebae38179d0fda97cf48cd540bd6b2645c87a7ed4c1e6b3585441be7b7e940096d03f4b08016540b3704fdc6d953168dff6a6b1a67818fed0f0d023433be", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "set-cookie": "uid=3582991558377661871; Domain=.p-td.com; Expires=Thu, 02-May-2013 13:29:53 GMT; Path=/" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:29:53 GMT" + } + ] + }, + { + "seqno": 218, + "wire": "886197dc34fd28a0195349fba820044a059b827ee36ca98b46ff7f1f842507417f7689861e458f716cee5b3f7f0db1acf4189eac2cb07f33a535dc618f1e3c2e3907277320f62f5152c78648c56cd6bf9a68fe7eaf6b83f9d3ab229a725ffe7f0f0d0232315f901d75d0620d263d4c741f71a0961ab4ff0f28d61c7000000aacc3bcba5dc606993fe77ca0c520323fb7d9bab5ebcd71f9dfdd9ddf6a5f3d2335502e3907277320f62f5153f6a60f359ac2a20dc34fd28a0195349fba820059502cdc13f71b654c5a37fda9ac699e063f", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:29:53 GMT" + }, + { + "connection": "close" + }, + { + "server": "AAWebServer" + }, + { + "p3p": "policyref=\"http://www.adadvisor.net/w3c/p3p.xml\",CP=\"NOI NID\"" + }, + { + "content-length": "21" + }, + { + "content-type": "application/javascript" + }, + { + "set-cookie": "ab=0001%3ATeN7H043oXvJ0Gd0I9Rzik4yxpbxTv3S; Domain=.adadvisor.net; Expires=Sat, 03 Nov 2013 13:29:53 GMT; Path=/" + } + ] + }, + { + "seqno": 219, + "wire": "cf7f00ccacf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a69c97d4bdc368d486fe81a97f0711a9af7423535eebe353570daa6e8740d4bbc3729af86d52f6ad0a69878a9934effe7f408290889aa06b48442ccac15cd524b6543a1790b4c85f2b90f4a815df5c220f28d190b4c85f3009a034f3cf3cf81a0b40136fb82001c105c6dc7c2275c642d3acb7f7ac699e063eef9e919aa81790b4c85f2bd454fde0f359ac2a20df3dbf4a0195b49fbac20084a099b8cbb719654c5a37ff6496df3dbf4a002a651d4a08007d4002e001700053168dff58bba8eb10649cbf551d6424f617ea9b5095ac2f71d0690692fd523f2b0e62c00faaec3f9f4b585ee3a0d20d25faa8eb26c1d4894f653f55d86ee3497fd00f1fbd9d29aee30c495d2bc85a642f95ea2a5890b490f54abf4ae6ff0a9b868d0aba490691dc92b349032eb7f12576da6920642fb4c81b7196c0d840f85b75ff0f0d01307691ca54a7d7f4eae25c4bf7100200880dff7f", + "headers": [ + { + ":status": "302" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI NID CURa ADMa DEVa PSAa PSDa OUR SAMa BUS PUR COM NAV INT\"" + }, + { + "dcs": "la-dcs-3-1.internal.demdex.com 1.9.12" + }, + { + "set-cookie": "demdex=24048888904140259620062165691276314735;Path=/;Domain=.demdex.net;Expires=Thu, 03-Nov-2022 23:37:33 GMT" + }, + { + "expires": "Thu, 01 Jan 2009 00:00:00 GMT" + }, + { + "cache-control": "no-cache,no-store,must-revalidate,max-age=0,proxy-revalidate,no-transform,private" + }, + { + "pragma": "no-cache" + }, + { + "location": "http://dpm.demdex.net/demconf.jpg?et:ibs%7cdata:dpid=375&dpuuid=3194305635051091579" + }, + { + "content-length": "0" + }, + { + "server": "Jetty(7.2.2.v20101205)" + } + ] + }, + { + "seqno": 220, + "wire": "88c27f029ba06b48442ce2ccae6a925b2a1d0bc85a642f95c87a540aefae117f0f28d192ba60134069e79e79f034168026df704003820b8db8f844eb8c85a7596fef58d33c0c7ddf3d2335502f2574af216990be57a8a9fbc1e6b3585441be7b7e94032b693f75840109413371976e32ca98b46fc1e4c0d240824251024f4b0f0d03333038c0", + "headers": [ + { + ":status": "200" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI NID CURa ADMa DEVa PSAa PSDa OUR SAMa BUS PUR COM NAV INT\"" + }, + { + "dcs": "la-dcs-6-3.internal.demdex.com 1.9.12" + }, + { + "set-cookie": "dpm=24048888904140259620062165691276314735;Path=/;Domain=.dpm.demdex.net;Expires=Thu, 03-Nov-2022 23:37:33 GMT" + }, + { + "expires": "Thu, 01 Jan 2009 00:00:00 GMT" + }, + { + "content-type": "image/jpeg" + }, + { + "cache-control": "no-cache,no-store,must-revalidate,max-age=0,proxy-revalidate,no-transform,private" + }, + { + "pragma": "no-cache" + }, + { + "sts": "OK" + }, + { + "content-length": "308" + }, + { + "server": "Jetty(7.2.2.v20101205)" + } + ] + }, + { + "seqno": 221, + "wire": "d6dddccbd30f28c2b4d240c85f699036e32d81b081f0b6ebff6a5f3d2335502e9b6ca9721e9fb53079acd615106f9edfa50025b40fd2c20059502cdc13f71b654c5a37fda9ac699e063f0f1fd09d29aee30c20b3525a92b566f25a17355dcc92d2590c35c87a5841531563b13516c8ad349fe563b13516cc97e06802f842088886449bb9600fc563b13516ce192fc0c85f699036e32d81b081f0b6ebffd8ca", + "headers": [ + { + ":status": "302" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "set-cookie": "uid=3194305635051091579; Domain=.turn.com; Expires=Thu, 02-May-2013 13:29:53 GMT; Path=/" + }, + { + "location": "http://segment-pixel.invitemedia.com/set_partner_uid?partnerID=402&sscs_active=1&partnerUID=3194305635051091579" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 13:29:53 GMT" + } + ] + }, + { + "seqno": 222, + "wire": "d6dddccbd30f28c2b4d240c85f699036e32d81b081f0b6ebff6a5f3d2335502e9b6ca9721e9fb53079acd615106f9edfa50025b40fd2c20059502cdc13f71b654c5a37fda9ac699e063f0f1fad9d29aee30c495d2bc85a642f95ea2a583468b9256692069d07c495db69a48190bed3206dc65b036103e16dd7ffd8ca", + "headers": [ + { + ":status": "302" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "set-cookie": "uid=3194305635051091579; Domain=.turn.com; Expires=Thu, 02-May-2013 13:29:53 GMT; Path=/" + }, + { + "location": "http://dpm.demdex.net/ibs:dpid=470&dpuuid=3194305635051091579" + }, + { + "transfer-encoding": "chunked" + }, + { + "date": "Sat, 03 Nov 2012 13:29:53 GMT" + } + ] + }, + { + "seqno": 223, + "wire": "88c47f009ba06b48442ce2cd2e6a925b2a1d0bc85a642f95c87a540aefae117f0f28d192ba60134069e79e79f034168026df704003820b8db8f844eb8c85a7596fef58d33c0c7ddf3d2335502f2574af216990be57a8a9fbc1e6b3585441be7b7e94032b693f75840109413371976e32ca98b46fc35f87352398ac4c697fc3d5c00f0d023432c2", + "headers": [ + { + ":status": "200" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI NID CURa ADMa DEVa PSAa PSDa OUR SAMa BUS PUR COM NAV INT\"" + }, + { + "dcs": "la-dcs-6-4.internal.demdex.com 1.9.12" + }, + { + "set-cookie": "dpm=24048888904140259620062165691276314735;Path=/;Domain=.dpm.demdex.net;Expires=Thu, 03-Nov-2022 23:37:33 GMT" + }, + { + "expires": "Thu, 01 Jan 2009 00:00:00 GMT" + }, + { + "content-type": "image/gif" + }, + { + "cache-control": "no-cache,no-store,must-revalidate,max-age=0,proxy-revalidate,no-transform,private" + }, + { + "pragma": "no-cache" + }, + { + "sts": "OK" + }, + { + "content-length": "42" + }, + { + "server": "Jetty(7.2.2.v20101205)" + } + ] + }, + { + "seqno": 224, + "wire": "d8cc0f28bc31e296c2f6b4b513d41f7ac699e063eef9e919aa80d577324b496430d721e9fbc1e6b3585441be7b7e940056ca3a960bee814002e001700153168dffd6d57f07e4acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe756fc8a5fddad4bdab6a90dfd07537c390ea6bdd09d4d7baf9d4bdab49d4d5c36a9ba1d075356fda75376fd6a70d3914d7c36a97b568534c3c54c9a77a97f0685376f854d7b70299f55efe7f5886a8eb10649cbf0f1fcb9d29aee30c1295e65e43db1d0525062755ea2a58acde4b47f931cf35058aa34901aaee649692c861fc4c73cd4162253f131cf35058a7a60fdc525447b9c7b62327cebd7a19ccf51a7c41070f0d0130cc7691ca54a7d7f4eaecae15fb8801081903bfdf", + "headers": [ + { + ":status": "302" + }, + { + "date": "Sat, 03 Nov 2012 13:29:53 GMT" + }, + { + "set-cookie": "io_frequency=;Path=/;Domain=invitemedia.com;Expires=Thu, 01-Jan-1970 00:00:01 GMT" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"OTI DSP COR ADMo TAIo PSAo PSDo CONo OUR SAMo OTRo STP UNI PUR COM NAV INT DEM STA PRE LOC\"" + }, + { + "cache-control": "no-cache" + }, + { + "location": "http://cm.g.doubleclick.net/pixel?google_nid=invitemedia&google_cm&google_hm=ZGdnc8YbR_itxPPM3K8lNw==" + }, + { + "content-length": "0" + }, + { + "connection": "close" + }, + { + "server": "Jetty(7.3.1.v20110307)" + } + ] + }, + { + "seqno": 225, + "wire": "db0f1fc49d29aee30c4cb566f25a17355dcc92d2590c35c87a589a91a49396cff2639e6a0b14c6920bd0e0dd8327ebbeaea060137c2dcade0e17f2209613e2639e6a0b113b96c8036196dc34fd280654d27eea0801128166e09fb8db4a62d1bfd9f65892a8eb10649cbf4a536a12b585ee3a0d20d25f5f92497ca589d34d1f6a1271d882a60e1bf0acf7768abc73f53154d0349272d90f0d033239337f288a0fda949e42c11d07275f", + "headers": [ + { + ":status": "302" + }, + { + "location": "http://g-pixel.invitemedia.com/gmatcher?google_gid=CAESEIZ7yBsa025UuJ5EUDIscrc&google_cver=1" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "text/html; charset=UTF-8" + }, + { + "server": "Cookie Matcher" + }, + { + "content-length": "293" + }, + { + "x-xss-protection": "1; mode=block" + } + ] + }, + { + "seqno": 226, + "wire": "88c20f28bc31e296c2f6b4b513d41f7ac699e063eef9e919aa80d577324b496430d721e9fbc1e6b3585441be7b7e940056ca3a960bee814002e001700153168dffdeddc6c5c40f0d023433d2c3", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "set-cookie": "io_frequency=;Path=/;Domain=invitemedia.com;Expires=Thu, 01-Jan-1970 00:00:01 GMT" + }, + { + "expires": "Thu, 01 Jan 1970 00:00:00 GMT" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"OTI DSP COR ADMo TAIo PSAo PSDo CONo OUR SAMo OTRo STP UNI PUR COM NAV INT DEM STA PRE LOC\"" + }, + { + "cache-control": "no-cache" + }, + { + "content-length": "43" + }, + { + "connection": "close" + }, + { + "server": "Jetty(7.3.1.v20110307)" + } + ] + }, + { + "seqno": 227, + "wire": "88e7e6d5dd0f28c6b4d240cb61136d884fbccb61759089f73ed4be7a466aa05c76c862d4429bb2e43d3f6a60f359ac2a20df3dbf4a004b681fa58400b2a059b827ee36d298b46ffb5358d33c0c7fc60f0d023433d4", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "set-cookie": "uid=3512552298351731296; Domain=.audienceiq.com; Expires=Thu, 02-May-2013 13:29:54 GMT; Path=/" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:29:53 GMT" + } + ] + }, + { + "seqno": 228, + "wire": "88e7e6d5dd0f28c6b4d240cbc17c2e32d3eebe27d965d13acfda97cf48cd540b8ed90c5a8853765c87a7ed4c1e6b3585441be7b7e940096d03f4b08016540b3704fdc6da53168dff6a6b1a67818fc60f0d023433c2", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "set-cookie": "uid=3819163497929337273; Domain=.audienceiq.com; Expires=Thu, 02-May-2013 13:29:54 GMT; Path=/" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + } + ] + }, + { + "seqno": 229, + "wire": "88c2769186b19272b025c4bb2a7f578b52756efeff7f07d8bdae0fe74eac8a5fddad4bdab6a97b86d521bfa0ea5fc1c4ea6bdd09d4d7baf9d4d5c36a9ba1d0752ef0dca70d3914d30f1fe7e94acf4189eac2cb07f33a535dc61848e642f1d1697a8ccb90f4b1e192315b35afe69a3f9fdf6496df3dbf4a002a5f29140befb4a05cb8005c0014c5a37f5895a47e561cc5801f4a547588324e5fa52a3ac849ec2f0f28b98fac8483c484fb50be6b3585441a0f57d280656be522c20044a059b827ee36d298b46ffb52b1a67818fb5243d2335502f1d1697a8ccb90f4ff40878faac82d9dcb67839591370f0d023632cb", + "headers": [ + { + ":status": "200" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "server": "Apache/2.2.3 (CentOS)" + }, + { + "p3p": "CP=\"NOI DSP COR CUR ADMo DEVo PSAo PSDo OUR SAMo BUS UNI NAV\", policyref=\"http://tags.bluekai.com/w3c/p3p.xml\"" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Thu, 01 Dec 1994 16:00:00 GMT" + }, + { + "cache-control": "max-age=0, no-cache, no-store" + }, + { + "set-cookie": "bkdc=wdc; expires=Mon, 03-Dec-2012 13:29:54 GMT; path=/; domain=.bluekai.com" + }, + { + "bk-server": "f325" + }, + { + "content-length": "62" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 230, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c81c0bbff5f4408cf2b0d15d454addcb620c7abf8769702ec8190bff4089f2b567f05b0b22d1fa868776b5f4e0df7f05a9bdae0fe6ef0dca5ee1b54bdab49d4c3934a9938df3a9ab4e753570daa6bc7cd4dd0e83a9bf0673ff3f4001738abda83a35ebddbef4207b0f0d8469f7dd6f5584704c81afcd6496e4593e9403aa693f7504008940bf71a7ae09d53168df7f1f88ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=430617" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "49975" + }, + { + "age": "62304" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 19:48:27 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 231, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b0340109a7bf5f88352398ac74acb37f768abda83a35ebddbef42073c7c6c57f058abda83a35ebddbef420730f0d846da101cfc4d36496e4593e9403aa693f750400894086e36ddc65e53168dfc3", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=402248" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "54206" + }, + { + "age": "62304" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 11:55:38 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 232, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034069913ffc2768abda83a35ebddbef4207bcbcac9c80f0d8465a089ffc7d66496e4593e9403aa693f75040089408ae320b817d4c5a37fc6", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=404329" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "34129" + }, + { + "age": "62304" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 12:30:19 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 233, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c81c107fc5768abda83a35ebddbef42077cecdcc7f058abda83a35ebddbef420770f0d846401743f5584704c819fdb6496e4593e9403aa693f7504008940bf71a7ae32253168dfcb", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=430621" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "30171" + }, + { + "age": "62303" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 19:48:32 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 234, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b0342684f3bfcac5d2d1d0cf0f0d8464400bdfc0dd6496e4593e9403aa693f7504008940bd7002b8dbca62d1bfcd", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=424287" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "32018" + }, + { + "age": "62303" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 18:02:58 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 235, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034275a137fccc4d4d3d2c30f0d84134179bfc2df6496e4593e9403aa693f7504008940bd71b6ee05c53168dfcf", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=427425" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "24185" + }, + { + "age": "62303" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 18:55:16 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 236, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b009d105d6bfcec9d6d5d4d30f0d846597da67c4e16496d07abe94036a693f75040089413371a76e34da98b46fd1", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=272174" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "33943" + }, + { + "age": "62303" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Mon, 05 Nov 2012 23:47:45 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 237, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b03226de743fd0cfd8d7d6ce0f0d846c4cb6cfd4e36496df697e94038a693f7504008940b571a15c682a62d1bfd3", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=325871" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "52353" + }, + { + "age": "62304" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Tue, 06 Nov 2012 14:42:41 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 238, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c802dbdfd2cddad9d8d70f0d8413eeb2e7d6e56496e4593e9403aa693f7504008940bf71a05c69e53168dfd5", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=430158" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "29736" + }, + { + "age": "62304" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 19:40:48 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 239, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c81f135fd4cfdcdbdad90f0d840b8d36ffd8e76496e4593e9403aa693f7504008940bf71b66e32d298b46fd7", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=430924" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "16459" + }, + { + "age": "62304" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 19:53:34 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 240, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b032f880267fd6d1dedddcdb0f0d840b8f3cf7cce96496e4593e9403aa693f75040089403f700ddc0b4a62d1bfd9", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=392023" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "16888" + }, + { + "age": "62303" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 09:05:14 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 241, + "wire": "88d0d7cfdfdeddce0f0d8465f0b2d7cdeaccd9", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=430621" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "39134" + }, + { + "age": "62303" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 19:48:32 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 242, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c81c0b9fd8d0e0dfdecf0f0d840bce38cfceebdbda", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=430616" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "18663" + }, + { + "age": "62303" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 19:48:27 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 243, + "wire": "88bed8768abda83a35ebddbef4206fe1e0df7f118abda83a35ebddbef4206f0f0d84109b69bfd0eddddc", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=430616" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA05" + }, + { + "content-length": "22545" + }, + { + "age": "62303" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 19:48:27 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 244, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b03207dc781fdbc0e3e2e1bf0f0d8469e75a73d1ee6496df697e94038a693f750400894082e04571b0a98b46ffde", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=309680" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA05" + }, + { + "content-length": "48746" + }, + { + "age": "62303" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Tue, 06 Nov 2012 10:12:51 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 245, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b032075a6d9fddd8e5e4e3e20f0d84644e36e7d3f06496df697e94038a693f75040089403f7196ee34d298b46fe0", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=307453" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "32656" + }, + { + "age": "62303" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Tue, 06 Nov 2012 09:35:44 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 246, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f00bfdfdae7e6e5e40f0d8369a75955840b8eb4f7f36496df3dbf4a01e5349fba820044a01eb8d3f700f298b46fe3", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431902" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "4473" + }, + { + "age": "16748" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Thu, 08 Nov 2012 08:49:08 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 247, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b03427da7c5fe2e1eae9e8e00f0d836c4cbf5584109d6c1f6196dc34fd280654d27eea0801128166e09fb8db4a62d1bf6496df3dbf4a01e5349fba820044a01cb827ae36e298b46fe7", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=429492" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA06" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA06" + }, + { + "content-length": "5239" + }, + { + "age": "22750" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Thu, 08 Nov 2012 06:28:56 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 248, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034271f741fe6e1eeedeceb0f0d8369d79a5584109d6c3fc16497df3dbf4a01e5349fba820044a01bb8d3971b654c5a37ffea", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=426970" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "4784" + }, + { + "age": "22751" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Thu, 08 Nov 2012 05:46:53 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 249, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85f69afe9cef1f0efcd0f0d8365e69955846c0d3edfc46496e4593e9403aa693f750400894133704edc132a62d1bfed", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431944" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA05" + }, + { + "content-length": "3843" + }, + { + "age": "50495" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 23:27:23 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 250, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c842d33fecd1f4f3f2d00f0d83680dbd55846c0db41fc76496e4593e9403aa693f750400894133702cdc0b8a62d1bff0", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431143" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA05" + }, + { + "content-length": "4058" + }, + { + "age": "50541" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 23:13:16 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 251, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c85d703fefe7408cf2b0d15d454addcb620c7abf8769702ec8190bff4089f2b567f05b0b22d1fa868776b5f4e0df4003703370a9bdae0fe6ef0dca5ee1b54bdab49d4c3934a9938df3a9ab4e753570daa6bc7cd4dd0e83a9bf0673ff3fe90f0d83644f87558479a740cfcd6496e4593e9403aa693f7504008940b371b6ae044a62d1bf408721eaa8a4498f5788ea52d6b0e83772ff", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=431761" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA07" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA07" + }, + { + "content-length": "3291" + }, + { + "age": "84703" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 13:54:12 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 252, + "wire": "88eef5dac3c2c1d90f0d8465a69b7f5584704c805fd06496e4593e9403aa693f7504008940bf71a7ae32ca98b46fc0", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=430621" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA05" + }, + { + "content-length": "34459" + }, + { + "age": "62302" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 19:48:33 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 253, + "wire": "8858a1aec3771a4bf4a547588324e5fa52bb0fe7d2d617b8e83483497e94a8eb2127b0bf4085aec1cd48ff86a8eb10649cbf5f87352398ac4c697f6c96df3dbf4a080a6a2254100215020b8276e36f298b46ff52848fd24a8f0f138efe40d4631965089e94840dc07f3f768dd06258741e54ad9326e61d5dbfcac90f28b9874ead37b1e6801f6a487a466aa022f4a2a5c87a7ed42f9acd615106fb4bf4a01c5b49fbac20044a059b827ee32053168dff6a5634cf031f7f6196dc34fd280654d27eea0801128166e09fb8db6a62d1bf0f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-cache, proxy-revalidate, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 20 Oct 2011 10:27:58 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"04baaef128fcc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "set-cookie": "ANONCHK=0; domain=c.msn.com; expires=Tue, 06-Nov-2012 13:29:30 GMT; path=/;" + }, + { + "date": "Sat, 03 Nov 2012 13:29:55 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 254, + "wire": "885892a8eb10649cbf4a536a12b585ee3a0d20d25f5f92497ca589d34d1f6a5e9c7620a982d4cab3df6c96c361be94036a6a225410022502fdc13f704f298b46ffc30f138efe401232dc6414a3649206e03f9fc254012acec20f0d03393534c7408a224a7aaa4ad416a9933f830befb96496c361be940054ca3a940bef814002e001700053168dff4086f2b58390d27f9bd6103a265a6995b784006db038fb2b5e13e0000000000003c270405a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "text/html; Charset=utf-8" + }, + { + "last-modified": "Fri, 05 Oct 2012 19:29:28 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"01c35bc2fa3cd1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:55 GMT" + }, + { + "content-length": "954" + }, + { + "pragma": "no-cache" + }, + { + "cteonnt-length": "1996" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "x-radid": "P10723443-T100550693-C29000000000082620" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 255, + "wire": "885899a8eb10649cbf4a54759093d85fa529b5095ac2f71d0690692fcccb64022d316c96d07abe940bca65b68504008540bf700cdc65d53168dfcb0f138ffe4627d913ae38ec8d364206e03f9fca7f2e8be393068dda78e800020039ca0f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001006" + }, + { + "date": "Sat, 03 Nov 2012 13:29:55 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 256, + "wire": "885894a8eb2127b0bf4a547588324e5fa52bb0ddc692ffd06496dc34fd2816d4d27eea08007940b97000b800298b46ff7f19e6acf4189eac2cb07f33a535dc61824952e392af285c87a58f0c918acf4189e98ad9ad7f34d1fcfd297b5c1fce9d5914bfbb5a97b56d521bfa14d7ba13a9af75f3a9ab86d3a9ba1d0753869da75356fda752ef0dca5ed5a14d30f152fe0d0a6edf0a9af6e0fe7f408cf2b794216aec3a4a4498f57f01300f28ff191d5d20cd2db03d2e26b77ff09dfa58c7f80d7fd7cc36dd5adf9f998373f3261bdfff701bfd310ecf1879eafff3bcf8f6b3bb76476e0108fc0f51ff08ffd7f9ef9a3e5877ff9bc3abffed1ffe1f670afb68f38f3f5ff5d7affedd63fef26dedf6a5634cf031f6a17cd66b0a8830d86fa50015b096358400b2a059b827ee36e298b46ffb5243d2335502e392af285c87a7ed4c694d7aaaa3d75f92497ca589d34d1f6a1271d882a60b532acf7f6196dc34fd280654d27eea0801128166e09fb8db8a62d1bf0f0d830b4d3b", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store, no-cache, private" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 15 Nov 2008 16:00:00 GMT" + }, + { + "p3p": "policyref=\"http://cdn.adnxs.com/w3c/policy/p3p.xml\", CP=\"NOI DSP COR ADM PSAo PSDo OURo SAMo UNRo OTRo BUS COM NAV DEM STA PRE\"" + }, + { + "x-xss-protection": "0" + }, + { + "set-cookie": "anj=Kfu=8fG4S]cvjr/?0P(*AuB-u**g1:XIFC`Ei'/AQwFYO^vhHR3SSI7:0ssX1ka!s@?zYs*/7]T1O`l^oQUpqMxHLk'kk[7/>IRq; path=/; expires=Fri, 01-Feb-2013 13:29:56 GMT; domain=.adnxs.com; HttpOnly" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "date": "Sat, 03 Nov 2012 13:29:56 GMT" + }, + { + "content-length": "1447" + } + ] + }, + { + "seqno": 257, + "wire": "88768c86b19272ad78fe8e92b015c37f03c6acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5ee1b46a5fc1c46a6f8720d4d7ba11a9af75f1a9938c2353271be353570daa64d37d4e1a7229a61e3fcff588ba6d4256b0bdc741a41a4bf6496d07abe94036a693f7504008940b3704fdc6dc53168df0f28ff1a94900a5a381463bfb8687bceac80c9e3f3bee6efd767e0f3bdbdec8639dfd7eb0ea665dbcdeb732de4c9e78cbdf469cdc5bedb75ef647601dcdc01e9c3456ecf7b7c7ef1cb76c67c11626c770edcd564df9b9fbd4cfbe7c5b1493e66a4dd82cd2fad8099f5429ec0fb52f9e919aa8174db654b90f4fda983cd66b0a8837cf6fd28012da07e961002ca8166e09fb8db8a62d1bfed4d634cf0315f95497ca58e83ee3412c3569fb24e3b1054c1c37e159e798624f6d5d4b27fce7b8b84842d695b05443c86aa6fd7", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "cache-control": "must-revalidate" + }, + { + "expires": "Mon, 05 Nov 2012 13:29:56 GMT" + }, + { + "set-cookie": "fc=2flUeaaDSas8xOI0IwXvS5DprXaL8T8Iioo9PyFO3fRY8uK-xitYHevMNKV5qRPT3ar07KU0y6i_uQzRwZVJBr3wc-cQ7FRKnITKYzO3zYV52dhK4dSErN9-EcLOAtq0; Domain=.turn.com; Expires=Thu, 02-May-2013 13:29:56 GMT; Path=/" + }, + { + "content-type": "text/javascript;charset=UTF-8" + }, + { + "transfer-encoding": "chunked" + }, + { + "content-encoding": "gzip" + }, + { + "vary": "Accept-Encoding" + }, + { + "date": "Sat, 03 Nov 2012 13:29:55 GMT" + } + ] + }, + { + "seqno": 258, + "wire": "88cedcdbcdccd90f138ffe4627d913ae38ec8d364206e03f9fd87f0c8be393068dda78e800020037d80f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001005" + }, + { + "date": "Sat, 03 Nov 2012 13:29:55 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 259, + "wire": "88cfdddccecdda0f138ffe4627d913ae38ec8d364206e03f9fd97e8be393068dda78e80002000bd90f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001002" + }, + { + "date": "Sat, 03 Nov 2012 13:29:55 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 260, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b032cba1137f5f88352398ac74acb37f768abda83a35ebddbef4207beae9e87f028abda83a35ebddbef4207b0f0d8469d680ff5584704c819ff86496df697e94038a693f7504008940bb71b05c0b8a62d1bfe8", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=337125" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA08" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA08" + }, + { + "content-length": "47409" + }, + { + "age": "62303" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Tue, 06 Nov 2012 17:50:16 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 261, + "wire": "890f0d0130dfe8e46496d07abe940054ca3a940bef814002e001700053168dff58b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfe5", + "headers": [ + { + ":status": "204" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:29:55 GMT" + }, + { + "connection": "keep-alive" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 262, + "wire": "88cecde65f91497ca589d34d1f649c7620a98386fc2b3dda58a0aec3771a4bf4a547588324e5fa52a3ac849ec2fd294da84ad617b8e83483497fd10f0d83682d0becca", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "text/html;charset=UTF-8" + }, + { + "content-encoding": "gzip" + }, + { + "cache-control": "private, no-cache, no-store, must-revalidate" + }, + { + "date": "Sat, 03 Nov 2012 13:29:56 GMT" + }, + { + "content-length": "4142" + }, + { + "connection": "keep-alive" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 263, + "wire": "8858aaaed8e8313e94a6d4256b0bdc741a41a4bf4a5761fcfa5ac2f71d0690692fd2948fcac398b034c802dbdfc7768abda83a35ebddbef4206ff3f2f17f078abda83a35ebddbef4206f0f0d84680d38d7c66196dc34fd280654d27eea0801128166e09fb8db4a62d1bf6496e4593e9403aa693f7504008940bf71a05c69f53168dff1", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "public, must-revalidate, proxy-revalidate, max-age=430158" + }, + { + "content-type": "image/jpeg" + }, + { + "server": "CO1MPPSTCA05" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "s": "CO1MPPSTCA05" + }, + { + "content-length": "40464" + }, + { + "age": "62303" + }, + { + "date": "Sat, 03 Nov 2012 13:29:54 GMT" + }, + { + "expires": "Wed, 07 Nov 2012 19:40:49 GMT" + }, + { + "connection": "keep-alive" + } + ] + }, + { + "seqno": 264, + "wire": "885886a8eb10649cbfeeeddf768dd06258741e54ad9326e61d5c1f7f17a7bdae0fe74ead2a5fddad4bdab6a97b86d1a9af742a6bdd7d4d5c36a97786e534c3c54ddbe1fe7ff9eb0f0d023433d2", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "server": "Microsoft-IIS/7.0" + }, + { + "p3p": "CP=\"NON DSP COR CURa PSA PSD OUR BUS NAV STA\"" + }, + { + "x-aspnet-version": "4.0.30319" + }, + { + "date": "Sat, 03 Nov 2012 13:29:55 GMT" + }, + { + "content-length": "43" + }, + { + "vary": "Accept-Encoding" + } + ] + }, + { + "seqno": 265, + "wire": "88f1f0efeeed0f138efe40d4631965089e94840dc07f3fecf8f70f28b9874ead37b1e6801f6a487a466aa022f4a2a5c87a7ed42f9acd615106fb4bf4a01c5b49fbac20044a059b827ee32053168dff6a5634cf031f7fd90f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-cache, proxy-revalidate, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 20 Oct 2011 10:27:58 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"04baaef128fcc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "set-cookie": "ANONCHK=0; domain=c.msn.com; expires=Tue, 06-Nov-2012 13:29:30 GMT; path=/;" + }, + { + "date": "Sat, 03 Nov 2012 13:29:56 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 266, + "wire": "88eae9e8ed0f138efe401232dc6414a3649206e03f9fece7f76196dc34fd280654d27eea0801128166e09fb8dbaa62d1bf0f0d83085c17f17f2883138f37e77f279bd61038e042cb4b6f0800db6f32fb6b5e7400000000000008401003e6", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "text/html; Charset=utf-8" + }, + { + "last-modified": "Fri, 05 Oct 2012 19:29:28 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"01c35bc2fa3cd1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:57 GMT" + }, + { + "content-length": "1162" + }, + { + "pragma": "no-cache" + }, + { + "cteonnt-length": "2685" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "x-radid": "P10661134-T100558395-C70000000000110100" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 267, + "wire": "88e5f3f2e4e3f00f138ffe4627d913ae38ec8d364206e03f9fefd4c00f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001005" + }, + { + "date": "Sat, 03 Nov 2012 13:29:57 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 268, + "wire": "88e5f3f2e4e3f00f138ffe4627d913ae38ec8d364206e03f9fef7f078be393068dda78e800020035dd0f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001004" + }, + { + "date": "Sat, 03 Nov 2012 13:29:56 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 269, + "wire": "88e6f4f3e5e4f10f138ffe4627d913ae38ec8d364206e03f9ff0d5c10f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001005" + }, + { + "date": "Sat, 03 Nov 2012 13:29:57 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 270, + "wire": "880f0d83132d39c10f1f9f9d29aee30c200b8a992a5ea2a58ee62f81c8c32e342640db01583e42bcc697c4f47689bf7b3e65a193777b3f5f87497ca589d34d1fe9", + "headers": [ + { + ":status": "200" + }, + { + "content-length": "2346" + }, + { + "date": "Sat, 03 Nov 2012 13:29:57 GMT" + }, + { + "location": "http://s0.2mdn.net/viewad/3642305/1-1x1.gif" + }, + { + "cache-control": "no-cache" + }, + { + "pragma": "no-cache" + }, + { + "server": "DCLK-AdSvr" + }, + { + "content-type": "text/html" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 271, + "wire": "885f8b497ca58e83ee3412c3569f6c96df697e9403ca681fa50400894102e01fb80754c5a37f6196c361be940094d27eea080112816ae320b807d4c5a37f6496dc34fd280654d27eea080112816ae320b807d4c5a37f4090f2b10f524b52564faacab1eb498f523f85a8e8a8d2cb768344b2970f0d8264417f288a0fda949e42c11d07275f5584784ebcf75890aed8e8313e94a47e561cc581e71a003fe1f2", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "text/javascript" + }, + { + "last-modified": "Tue, 08 May 2012 20:09:07 GMT" + }, + { + "date": "Fri, 02 Nov 2012 14:30:09 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 14:30:09 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "321" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "82788" + }, + { + "cache-control": "public, max-age=86400" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 272, + "wire": "885f87352398ac4c697f6c96df3dbf4a09a5340fd2820044a0817022b8db8a62d1bf6196c361be940094d27eea0801128205c6c3700053168dff6496dc34fd280654d27eea0801128205c6c3700053168dffc6c50f0d840b8cbeffc455846df7d97bc3e6f7", + "headers": [ + { + ":status": "200" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 24 May 2012 20:12:56 GMT" + }, + { + "date": "Fri, 02 Nov 2012 20:51:00 GMT" + }, + { + "expires": "Sat, 03 Nov 2012 20:51:00 GMT" + }, + { + "x-content-type-options": "nosniff" + }, + { + "server": "sffe" + }, + { + "content-length": "16399" + }, + { + "x-xss-protection": "1; mode=block" + }, + { + "age": "59938" + }, + { + "cache-control": "public, max-age=86400" + }, + { + "vary": "Accept-Encoding" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 273, + "wire": "890f0d0130d1408721eaa8a4498f5788ea52d6b0e83772ff4085aec1cd48ff86a8eb10649cbfdfdec4", + "headers": [ + { + ":status": "204" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:29:57 GMT" + }, + { + "connection": "keep-alive" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 274, + "wire": "8858a1aec3771a4bf4a547588324e5fa52bb0fe7d2d617b8e83483497e94a8eb2127b0bfbfc56c96df3dbf4a080a6a2254100215020b8276e36f298b46ff52848fd24a8f0f138efe40d4631965089e94840dc07f3f768dd06258741e54ad9326e61d5dbf4089f2b567f05b0b22d1fa868776b5f4e0df7f1aa9bdae0fe6ef0dca5ee1b54bdab49d4c3934a9938df3a9ab4e753570daa6bc7cd4dd0e83a9bf0673ff3f0f28b9874ead37b1e6801f6a487a466aa022f4a2a5c87a7ed42f9acd615106fb4bf4a01c5b49fbac20044a059b827ee32053168dff6a5634cf031f7f6196dc34fd280654d27eea0801128166e09fb8dbca62d1bf0f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-cache, proxy-revalidate, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 20 Oct 2011 10:27:58 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"04baaef128fcc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "set-cookie": "ANONCHK=0; domain=c.msn.com; expires=Tue, 06-Nov-2012 13:29:30 GMT; path=/;" + }, + { + "date": "Sat, 03 Nov 2012 13:29:58 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 275, + "wire": "885892a8eb10649cbf4a536a12b585ee3a0d20d25f5f92497ca589d34d1f6a5e9c7620a982d4cab3df6c96c361be94036a6a225410022502fdc13f704f298b46ffc50f138efe401232dc6414a3649206e03f9fc454012ac36196dc34fd280654d27eea0801128166e09fb8dbea62d1bf0f0d03393533ca7f1f830befb96496c361be940054ca3a940bef814002e001700053168dff7f209bd6103a265a6995b784006db038fb2b5e13e0000000000003c270405a839bd9ab", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "text/html; Charset=utf-8" + }, + { + "last-modified": "Fri, 05 Oct 2012 19:29:28 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"01c35bc2fa3cd1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:59 GMT" + }, + { + "content-length": "953" + }, + { + "pragma": "no-cache" + }, + { + "cteonnt-length": "1996" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "x-radid": "P10723443-T100550693-C29000000000082620" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 276, + "wire": "885899a8eb10649cbf4a54759093d85fa529b5095ac2f71d0690692fcfd564022d316c96d07abe940bca65b68504008540bf700cdc65d53168dfce0f138ffe4627d913ae38ec8d364206e03f9fcd7f248be393068dda78e800020039cb0f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001006" + }, + { + "date": "Sat, 03 Nov 2012 13:29:58 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 277, + "wire": "885894a8eb2127b0bf4a547588324e5fa52bb0ddc692ffd36496dc34fd2816d4d27eea08007940b97000b800298b46ff7f0fe6acf4189eac2cb07f33a535dc61824952e392af285c87a58f0c918acf4189e98ad9ad7f34d1fcfd297b5c1fce9d5914bfbb5a97b56d521bfa14d7ba13a9af75f3a9ab86d3a9ba1d0753869da75356fda752ef0dca5ed5a14d30f152fe0d0a6edf0a9af6e0fe7f7f1f01300f28ff1a1d5d20cd2db03d2e217ffcb09dfa58c7f80d7fd7cc36dd5adf9f998373f325d8b3e037fa621d9e30f3d5ffe779f1ed6776ec8edc0211f8193a9e1d44ffdffafff3f8fcb550ecf9fff9be1d20c18afeeffbdb56af2f4deae3a9a797b07261ef5f6a5634cf031f6a17cd66b0a8830d86fa50015b096358400b2a059b827ee36fa98b46ffb5243d2335502e392af285c87a7ed4c694d7aaaa3d7f5f92497ca589d34d1f6a1271d882a60b532acf7fcb0f0d03353339", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store, no-cache, private" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 15 Nov 2008 16:00:00 GMT" + }, + { + "p3p": "policyref=\"http://cdn.adnxs.com/w3c/policy/p3p.xml\", CP=\"NOI DSP COR ADM PSAo PSDo OURo SAMo UNRo OTRo BUS COM NAV DEM STA PRE\"" + }, + { + "x-xss-protection": "0" + }, + { + "set-cookie": "anj=Kfu=8fG2RnOx8gy:7tmWz0W/8y; path=/; expires=Fri, 01-Feb-2013 13:29:59 GMT; domain=.adnxs.com; HttpOnly" + }, + { + "content-type": "text/html; charset=utf-8" + }, + { + "date": "Sat, 03 Nov 2012 13:29:59 GMT" + }, + { + "content-length": "539" + } + ] + }, + { + "seqno": 278, + "wire": "88c6d7ddc5c4d40f138ffe4627d913ae38ec8d364206e03f9fd37f048be393068dda78e800020037d10f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001005" + }, + { + "date": "Sat, 03 Nov 2012 13:29:58 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 279, + "wire": "88c7d8dec6c5d50f138ffe4627d913ae38ec8d364206e03f9fd47e8be393068dda78e800020033cd0f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001003" + }, + { + "date": "Sat, 03 Nov 2012 13:29:59 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 280, + "wire": "885886a8eb2127b0bfeaca6401307b8b84842d695b05443c86aa6f4086f2b5281c86938e640003cfb4d01713efbecb4f34f7d67f1f842507417f0f0d03353038", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store" + }, + { + "content-type": "text/html" + }, + { + "content-encoding": "gzip" + }, + { + "expires": "0" + }, + { + "vary": "Accept-Encoding" + }, + { + "x-msadid": "300089440.299934848" + }, + { + "date": "Sat, 03 Nov 2012 13:29:58 GMT" + }, + { + "connection": "close" + }, + { + "content-length": "508" + } + ] + }, + { + "seqno": 281, + "wire": "88768c86b19272ad78fe8e92b015c37f09c6acf4189eac2cb07f2c78648c56cd6bf9a68fe7e94bdae0fe74eac8a5ee1b46a5fc1c46a6f8720d4d7ba11a9af75f1a9938c2353271be353570daa64d37d4e1a7229a61e3fcff58b1a47e561cc5801f4a547588324e5fa52a3ac849ec2fd295d86ee3497e94a6d4256b0bdc741a41a4bf4a216a47e47316007fe10f28c2b4d240c85f699036e32d81b081f0b6ebff6a5f3d2335502e9b6ca9721e9fb53079acd615106f9edfa50025b40fd2c20059502cdc13f71b7d4c5a37fda9ac699e063fe70f0d023433da", + "headers": [ + { + ":status": "200" + }, + { + "server": "Apache-Coyote/1.1" + }, + { + "p3p": "policyref=\"/w3c/p3p.xml\", CP=\"NOI CURa DEVa TAIa PSAa PSDa IVAa IVDa OUR IND UNI NAV\"" + }, + { + "cache-control": "max-age=0, no-cache, no-store, private, must-revalidate, s-maxage=0" + }, + { + "pragma": "no-cache" + }, + { + "set-cookie": "uid=3194305635051091579; Domain=.turn.com; Expires=Thu, 02-May-2013 13:29:59 GMT; Path=/" + }, + { + "content-type": "image/gif" + }, + { + "content-length": "43" + }, + { + "date": "Sat, 03 Nov 2012 13:29:58 GMT" + } + ] + }, + { + "seqno": 282, + "wire": "890f0d0130d5e2e16496d07abe940054ca3a940bef814002e001700053168dff58b0aec3771a4bf4a547588324e5fa52a3ac419272c1b8a95af1cfd4c5fa52a3ac849ec2fd295d87f3e96b0bdc741a41a4bfe9", + "headers": [ + { + ":status": "204" + }, + { + "content-length": "0" + }, + { + "date": "Sat, 03 Nov 2012 13:29:59 GMT" + }, + { + "connection": "keep-alive" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Mon, 01 Jan 1990 00:00:00 GMT" + }, + { + "cache-control": "private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate" + }, + { + "content-type": "image/gif" + } + ] + }, + { + "seqno": 283, + "wire": "88e2e3e9e1e00f138efe40d4631965089e94840dc07f3fdfdedd0f28b9874ead37b1e6801f6a487a466aa022f4a2a5c87a7ed42f9acd615106fb4bf4a01c5b49fbac20044a059b827ee32053168dff6a5634cf031f7f6196dc34fd280654d27eea0801128166e320b800298b46ff0f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "private, no-cache, proxy-revalidate, no-store" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "last-modified": "Thu, 20 Oct 2011 10:27:58 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"04baaef128fcc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "x-powered-by": "ASP.NET" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "set-cookie": "ANONCHK=0; domain=c.msn.com; expires=Tue, 06-Nov-2012 13:29:30 GMT; path=/;" + }, + { + "date": "Sat, 03 Nov 2012 13:30:00 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 284, + "wire": "88dcdbdae10f138efe401232dc6414a3649206e03f9fe0d9ded80f0d03393534e47f18831000f7d7d6d5", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, must-revalidate" + }, + { + "content-type": "text/html; Charset=utf-8" + }, + { + "last-modified": "Fri, 05 Oct 2012 19:29:28 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"01c35bc2fa3cd1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "access-control-allow-origin": "*" + }, + { + "p3p": "CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"" + }, + { + "date": "Sat, 03 Nov 2012 13:29:59 GMT" + }, + { + "content-length": "954" + }, + { + "pragma": "no-cache" + }, + { + "cteonnt-length": "2008" + }, + { + "expires": "Fri, 01 Jan 1990 00:00:00 GMT" + }, + { + "x-radid": "P10723443-T100550693-C29000000000082620" + }, + { + "content-encoding": "gzip" + } + ] + }, + { + "seqno": 285, + "wire": "88d4e5ebd3d2e20f138ffe4627d913ae38ec8d364206e03f9fe1cbd90f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001005" + }, + { + "date": "Sat, 03 Nov 2012 13:29:59 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 286, + "wire": "88d4e5ebd3d2e20f138ffe4627d913ae38ec8d364206e03f9fe1f7d90f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001004" + }, + { + "date": "Sat, 03 Nov 2012 13:29:59 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 287, + "wire": "88d4e5ebd3d2e20f138ffe4627d913ae38ec8d364206e03f9fe17f0b8be393068dda78e800020007c00f0d023432", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-cache, no-store, must-revalidate" + }, + { + "pragma": "no-cache" + }, + { + "content-type": "image/gif" + }, + { + "expires": "-1" + }, + { + "last-modified": "Mon, 18 Jul 2011 19:03:37 GMT" + }, + { + "accept-ranges": "bytes" + }, + { + "etag": "\"a29327667d45cc1:0\"" + }, + { + "server": "Microsoft-IIS/7.5" + }, + { + "s": "VIEMSNVM001001" + }, + { + "date": "Sat, 03 Nov 2012 13:30:00 GMT" + }, + { + "content-length": "42" + } + ] + }, + { + "seqno": 288, + "wire": "88d1e6d0cfce0f28ff231d5d20cd2db03d2e273ae2277e9631fe035ff5f30db756b7e7e660dcfcc97fbb980dfe9887678c3cf57ff9de7c7b59ddbb23b700847e064a0191887ff500ac4ffdffbfeeb6bfc6ffc9725bfff35fef5475b9e7fbc9aac63e747ffda7fb47b1fff9f3303981ed9c66c7f6a5634cf031f6a17cd66b0a8830d86fa50015b096358400b2a059b8c82e000a62d1bfed490f48cd540b8e4abca1721e9fb531a535eaaa8f5fcdc00f0d03353336", + "headers": [ + { + ":status": "200" + }, + { + "cache-control": "no-store, no-cache, private" + }, + { + "pragma": "no-cache" + }, + { + "expires": "Sat, 15 Nov 2008 16:00:00 GMT" + }, + { + "p3p": "policyref=\"http://cdn.adnxs.com/w3c/policy/p3p.xml\", CP=\"NOI DSP COR ADM PSAo PSDo OURo SAMo UNRo OTRo BUS COM NAV DEM STA PRE\"" + }, + { + "x-xss-protection": "0" + }, + { + "set-cookie": "anj=Kfu=8fG6kGcvjr/?0P(*AuB-u**g1:XIDv6Ei'/AQwFYO^vhHR3SSI7:0ssX1dl0I/A@=2rt>+)p4?5?fIu+)p4?5?fIu+)p4?5?fIu+)p4?5?fIu Date: Fri, 5 Dec 2025 14:01:16 -0600 Subject: [PATCH 15/60] Add h2 codec tests --- .../java/http/client/h2/H2FrameCodecTest.java | 462 ++++++++++++++++++ .../http/client/h2/H2FrameTestSuiteTest.java | 288 +++++++++++ .../resources/http2-frame-test-case/LICENSE | 22 + .../continuation/header.json | 14 + .../continuation/normal.json | 14 + .../http2-frame-test-case/data/normal.json | 16 + .../error/data-frame-padding.json | 8 + .../error/data-frame-size.json | 8 + .../error/data-frame-stream.json | 8 + .../error/goaway-frame-size.json | 8 + .../error/goaway-frame-stream.json | 8 + .../error/headers-frame-padding.json | 8 + .../error/headers-frame-stream.json | 8 + .../error/ping-frame-size.json | 8 + .../error/ping-frame-stream.json | 8 + .../error/priority-frame-size.json | 8 + .../error/priority-frame-stream.json | 8 + .../error/push_promise-frame-padding.json | 9 + ...ush_promise-frame-promised_stream-odd.json | 8 + ...sh_promise-frame-promised_stream-zero.json | 8 + .../error/push_promise-frame-stream.json | 8 + .../error/rst_stream-frame-size.json | 8 + .../error/rst_stream-frame-stream.json | 8 + .../error/settings-frame-ack-size.json | 8 + .../error/settings-frame-size.json | 8 + .../error/settings-frame-stream.json | 8 + .../error/window_update-frame-increment.json | 8 + .../error/window_update-frame-size.json | 8 + .../http2-frame-test-case/goaway/normal.json | 16 + .../http2-frame-test-case/headers/normal.json | 19 + .../headers/priority.json | 19 + .../http2-frame-test-case/ping/.gikeep | 0 .../http2-frame-test-case/ping/normal.json | 14 + .../priority/normal.json | 18 + .../push_promise/normal.json | 17 + .../rst_stream/normal.json | 14 + .../settings/normal.json | 23 + .../window_update/normal.json | 14 + 38 files changed, 1147 insertions(+) create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameCodecTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameTestSuiteTest.java create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/LICENSE create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/continuation/header.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/continuation/normal.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/data/normal.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/data-frame-padding.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/data-frame-size.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/data-frame-stream.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/goaway-frame-size.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/goaway-frame-stream.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/headers-frame-padding.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/headers-frame-stream.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/ping-frame-size.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/ping-frame-stream.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/priority-frame-size.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/priority-frame-stream.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-padding.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-promised_stream-odd.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-promised_stream-zero.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-stream.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/rst_stream-frame-size.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/rst_stream-frame-stream.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/settings-frame-ack-size.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/settings-frame-size.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/settings-frame-stream.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/window_update-frame-increment.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/error/window_update-frame-size.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/goaway/normal.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/headers/normal.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/headers/priority.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/ping/.gikeep create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/ping/normal.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/priority/normal.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/push_promise/normal.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/rst_stream/normal.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/settings/normal.json create mode 100644 http/http-client/src/test/resources/http2-frame-test-case/window_update/normal.json diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameCodecTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameCodecTest.java new file mode 100644 index 000000000..32d79c639 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameCodecTest.java @@ -0,0 +1,462 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +class H2FrameCodecTest { + + // Write helper methods + @Test + void writeSettings() throws IOException { + var out = new ByteArrayOutputStream(); + codec(out).writeSettings(1, 4096, 3, 100); + var frame = decode(out); + int[] s = frame.parseSettings(); + + assertEquals(4, s.length); + assertEquals(1, s[0]); + assertEquals(4096, s[1]); + assertEquals(3, s[2]); + assertEquals(100, s[3]); + } + + @Test + void writeSettingsAck() throws IOException { + var out = new ByteArrayOutputStream(); + codec(out).writeSettingsAck(); + var frame = decode(out); + + assertEquals(4, frame.type()); + assertEquals(1, frame.flags()); + assertEquals(0, frame.payloadLength()); + assertEquals(0, frame.streamId()); + } + + @Test + void writeGoaway() throws IOException { + var out = new ByteArrayOutputStream(); + codec(out).writeGoaway(5, 2, "debug"); + var frame = decode(out); + int[] g = frame.parseGoaway(); + + assertEquals(5, g[0]); + assertEquals(2, g[1]); + } + + @Test + void writeGoawayNullDebug() throws IOException { + var out = new ByteArrayOutputStream(); + codec(out).writeGoaway(1, 0, null); + var frame = decode(out); + + assertEquals(7, frame.type()); + assertEquals(0, frame.streamId()); + assertEquals(8, frame.payloadLength()); + } + + @Test + void writeWindowUpdate() throws IOException { + var out = new ByteArrayOutputStream(); + codec(out).writeWindowUpdate(1, 65535); + var frame = decode(out); + + assertEquals(65535, frame.parseWindowUpdate()); + } + + @Test + void writeRstStream() throws IOException { + var out = new ByteArrayOutputStream(); + codec(out).writeRstStream(1, 8); + var frame = decode(out); + + assertEquals(8, frame.parseRstStream()); + } + + @Test + void writeHeadersWithContinuation() throws IOException { + var out = new ByteArrayOutputStream(); + var codec = new H2FrameCodec(new ByteArrayInputStream(new byte[0]), out, 16); + byte[] block = new byte[50]; + codec.writeHeaders(1, block, 0, 50, true); + codec.flush(); + + var readCodec = + new H2FrameCodec(new ByteArrayInputStream(out.toByteArray()), new ByteArrayOutputStream(), 16384); + var frame = readCodec.readFrame(); + byte[] result = readCodec.readHeaderBlock(frame); + + assertEquals(50, result.length); + } + + @Test + void writeHeadersSingleFrame() throws IOException { + var out = new ByteArrayOutputStream(); + codec(out).writeHeaders(1, new byte[] {1, 2, 3}, 0, 3, false); + var frame = decode(out); + + assertTrue(frame.hasFlag(0x04)); // END_HEADERS + } + + // Validation + @Test + void throwsOnNegativeStreamId() { + assertThrows(IllegalArgumentException.class, + () -> codec(new ByteArrayOutputStream()).writeFrame(0, 0, -1, new byte[0])); + } + + @Test + void throwsOnPayloadExceedsMax() { + var codec = new H2FrameCodec(new ByteArrayInputStream(new byte[0]), new ByteArrayOutputStream(), 100); + + assertThrows(H2Exception.class, () -> codec.writeFrame(0, 0, 1, new byte[200])); + } + + @Test + void throwsOnWindowUpdateZero() { + assertThrows(IllegalArgumentException.class, () -> codec(new ByteArrayOutputStream()).writeWindowUpdate(1, 0)); + } + + @Test + void throwsOnOddSettingsCount() { + assertThrows(IllegalArgumentException.class, () -> codec(new ByteArrayOutputStream()).writeSettings(1, 2, 3)); + } + + // readHeaderBlock + @Test + void readHeaderBlockWithContinuation() throws IOException { + var out = new ByteArrayOutputStream(); + out.write(buildFrame(1, 0, 1, new byte[] {1, 2})); // HEADERS no END_HEADERS + out.write(buildFrame(9, 0x04, 1, new byte[] {3, 4})); // CONTINUATION with END_HEADERS + + var codec = new H2FrameCodec(new ByteArrayInputStream(out.toByteArray()), new ByteArrayOutputStream(), 16384); + var frame = codec.readFrame(); + byte[] block = codec.readHeaderBlock(frame); + + assertArrayEquals(new byte[] {1, 2, 3, 4}, block); + } + + @Test + void throwsOnContinuationWrongStream() throws IOException { + var out = new ByteArrayOutputStream(); + out.write(buildFrame(1, 0, 1, new byte[] {1})); + out.write(buildFrame(9, 0x04, 2, new byte[] {2})); // wrong stream + var codec = new H2FrameCodec(new ByteArrayInputStream(out.toByteArray()), new ByteArrayOutputStream(), 16384); + + assertThrows(H2Exception.class, () -> codec.readHeaderBlock(codec.readFrame())); + } + + @Test + void throwsOnNonContinuationInterrupt() throws IOException { + var out = new ByteArrayOutputStream(); + out.write(buildFrame(1, 0, 1, new byte[] {1})); + out.write(buildFrame(0, 0, 1, new byte[] {2})); // DATA interrupts + var codec = new H2FrameCodec(new ByteArrayInputStream(out.toByteArray()), new ByteArrayOutputStream(), 16384); + + assertThrows(H2Exception.class, () -> codec.readHeaderBlock(codec.readFrame())); + } + + @Test + void throwsOnEofDuringContinuation() throws IOException { + var out = new ByteArrayOutputStream(); + out.write(buildFrame(1, 0, 1, new byte[] {1})); + var codec = new H2FrameCodec(new ByteArrayInputStream(out.toByteArray()), new ByteArrayOutputStream(), 16384); + + assertThrows(IOException.class, () -> codec.readHeaderBlock(codec.readFrame())); + } + + @Test + void readHeaderBlockFromPushPromise() throws IOException { + byte[] payload = {0, 0, 0, 2, 'a', 'b'}; + var codec = new H2FrameCodec(new ByteArrayInputStream(buildFrame(5, 0x04, 1, payload)), + new ByteArrayOutputStream(), + 16384); + byte[] block = codec.readHeaderBlock(codec.readFrame()); + + assertArrayEquals(new byte[] {'a', 'b'}, block); + } + + // Padding/Priority edge cases + @Test + void throwsOnPadLengthExceedsPayload() { + byte[] payload = {10, 'a'}; + + assertThrows(H2Exception.class, () -> decode(buildFrame(0, 0x08, 1, payload))); + } + + @Test + void throwsOnPriorityHeadersTooShort() { + assertThrows(H2Exception.class, () -> decode(buildFrame(1, 0x24, 1, new byte[3]))); + } + + @Test + void removePaddingFromPushPromise() throws IOException { + // PUSH_PROMISE (type 5) with PADDED flag (0x08), pad=1, promised stream=2, header block='x' + byte[] payload = {1, 0, 0, 0, 2, 'x', 0}; // padLen=1, promisedId=2, data='x', padding=0 + var codec = new H2FrameCodec(new ByteArrayInputStream(buildFrame(5, 0x0C, 1, payload)), + new ByteArrayOutputStream(), + 16384); + var frame = codec.readFrame(); + + assertEquals(5, frame.type()); + assertEquals(5, frame.payloadLength()); // promisedId(4) + 'x'(1) + byte[] headerBlock = codec.readHeaderBlock(frame); + assertArrayEquals(new byte[] {'x'}, headerBlock); + } + + @Test + void removePriorityFromHeaders() throws IOException { + // HEADERS with PRIORITY flag - 5 bytes priority + header block + byte[] payload = {0, 0, 0, 1, 16, 'a', 'b'}; // dependency=1, weight=16, headers='ab' + var frame = decode(buildFrame(1, 0x24, 1, payload)); // PRIORITY | END_HEADERS + + assertEquals(2, frame.payloadLength()); + assertArrayEquals(new byte[] {'a', 'b'}, Arrays.copyOfRange(frame.payload(), 0, frame.payloadLength())); + } + + // validateFrameSize tests + @Test + void throwsOnPingWrongSize() { + assertThrows(H2Exception.class, () -> decode(buildFrame(6, 0, 0, new byte[4]))); + } + + @Test + void throwsOnSettingsAckNonEmpty() { + assertThrows(H2Exception.class, () -> decode(buildFrame(4, 0x01, 0, new byte[6]))); + } + + @Test + void throwsOnWindowUpdateWrongSize() { + assertThrows(H2Exception.class, () -> decode(buildFrame(8, 0, 0, new byte[3]))); + } + + @Test + void throwsOnRstStreamWrongSize() { + assertThrows(H2Exception.class, () -> decode(buildFrame(3, 0, 1, new byte[3]))); + } + + @Test + void throwsOnPriorityWrongSize() { + assertThrows(H2Exception.class, () -> decode(buildFrame(2, 0, 1, new byte[4]))); + } + + @Test + void throwsOnGoawayTooShort() { + assertThrows(H2Exception.class, () -> decode(buildFrame(7, 0, 0, new byte[7]))); + } + + @Test + void throwsOnPushPromiseTooShort() { + assertThrows(H2Exception.class, () -> decode(buildFrame(5, 0, 1, new byte[3]))); + } + + @Test + void throwsOnPushPromisePaddedTooShort() { + assertThrows(H2Exception.class, () -> decode(buildFrame(5, 0x08, 1, new byte[4]))); + } + + // validateStreamId tests + @Test + void throwsOnDataStreamIdZero() { + assertThrows(H2Exception.class, () -> decode(buildFrame(0, 0, 0, new byte[1]))); + } + + @Test + void throwsOnHeadersStreamIdZero() { + assertThrows(H2Exception.class, () -> decode(buildFrame(1, 0x04, 0, new byte[1]))); + } + + @Test + void throwsOnPriorityStreamIdZero() { + assertThrows(H2Exception.class, () -> decode(buildFrame(2, 0, 0, new byte[5]))); + } + + @Test + void throwsOnRstStreamStreamIdZero() { + assertThrows(H2Exception.class, () -> decode(buildFrame(3, 0, 0, new byte[4]))); + } + + @Test + void throwsOnContinuationStreamIdZero() { + assertThrows(H2Exception.class, () -> decode(buildFrame(9, 0, 0, new byte[1]))); + } + + @Test + void throwsOnSettingsNonZeroStreamId() { + assertThrows(H2Exception.class, () -> decode(buildFrame(4, 0, 1, new byte[0]))); + } + + @Test + void throwsOnPingNonZeroStreamId() { + assertThrows(H2Exception.class, () -> decode(buildFrame(6, 0, 1, new byte[8]))); + } + + @Test + void throwsOnGoawayNonZeroStreamId() { + assertThrows(H2Exception.class, () -> decode(buildFrame(7, 0, 1, new byte[8]))); + } + + @Test + void throwsOnPushPromiseStreamIdZero() { + assertThrows(H2Exception.class, () -> decode(buildFrame(5, 0x04, 0, new byte[4]))); + } + + // Frame size exceeds max during read + @Test + void throwsOnFrameSizeExceedsMax() { + var codec = new H2FrameCodec(new ByteArrayInputStream(buildFrame(0, 0, 1, new byte[200])), + new ByteArrayOutputStream(), + 100); + + assertThrows(H2Exception.class, codec::readFrame); + } + + // removePadding edge case - empty payload + @Test + void throwsOnPaddedEmptyPayload() { + assertThrows(H2Exception.class, () -> decode(buildFrame(0, 0x08, 1, new byte[0]))); + } + + // PUSH_PROMISE payload too short for promised stream ID (in readHeaderBlock) + @Test + void throwsOnPushPromisePayloadTooShortForStreamId() throws IOException { + // Create a Frame directly with short payload to test readHeaderBlock path + var frame = new H2FrameCodec.Frame(5, 0x04, 1, new byte[2], 2); + var codec = new H2FrameCodec(new ByteArrayInputStream(new byte[0]), new ByteArrayOutputStream(), 16384); + + assertThrows(H2Exception.class, () -> codec.readHeaderBlock(frame)); + } + + // Frame.parseSettings edge cases + @Test + void parseSettingsWrongFrameType() throws IOException { + var frame = decode(buildFrame(0, 0, 1, new byte[1])); // DATA frame + assertThrows(H2Exception.class, frame::parseSettings); + } + + @Test + void parseSettingsPayloadNotMultipleOf6() throws IOException { + var frame = decode(buildFrame(4, 0, 0, new byte[7])); // 7 bytes, not multiple of 6 + + assertThrows(H2Exception.class, frame::parseSettings); + } + + // Frame.parseGoaway edge cases + @Test + void parseGoawayWrongFrameType() throws IOException { + var frame = decode(buildFrame(0, 0, 1, new byte[1])); + + assertThrows(H2Exception.class, frame::parseGoaway); + } + + @Test + void parseGoawayPayloadTooShort() { + var frame = new H2FrameCodec.Frame(7, 0, 0, new byte[4], 4); + + assertThrows(H2Exception.class, frame::parseGoaway); + } + + // Frame.parseWindowUpdate edge cases + @Test + void parseWindowUpdateWrongFrameType() throws IOException { + var frame = decode(buildFrame(0, 0, 1, new byte[1])); + + assertThrows(H2Exception.class, frame::parseWindowUpdate); + } + + @Test + void parseWindowUpdateWrongPayloadLength() { + // WINDOW_UPDATE frame with 3-byte payload instead of required 4 + var frame = new H2FrameCodec.Frame(8, 0, 1, new byte[3], 3); + + assertThrows(H2Exception.class, frame::parseWindowUpdate); + } + + @Test + void parseWindowUpdateZeroIncrement() throws IOException { + var frame = decode(buildFrame(8, 0, 1, new byte[4])); // all zeros = increment 0 + + assertThrows(H2Exception.class, frame::parseWindowUpdate); + } + + // Frame.parseRstStream edge cases + @Test + void parseRstStreamWrongFrameType() throws IOException { + var frame = decode(buildFrame(0, 0, 1, new byte[1])); + + assertThrows(H2Exception.class, frame::parseRstStream); + } + + @Test + void parseRstStreamWrongPayloadLength() throws IOException { + var frame = new H2FrameCodec.Frame(3, 0, 1, new byte[3], 3); + + assertThrows(H2Exception.class, frame::parseRstStream); + } + + // Incomplete payload reads + @Test + void throwsOnIncompleteControlFramePayload() { + // Build header claiming 8-byte PING payload but only provide 4 bytes + byte[] truncated = new byte[9 + 4]; // header + partial payload + truncated[2] = 8; // length = 8 + truncated[3] = 6; // type = PING + // streamId = 0 (already zeros) + var codec = new H2FrameCodec(new ByteArrayInputStream(truncated), new ByteArrayOutputStream(), 16384); + + assertThrows(IOException.class, codec::readFrame); + } + + @Test + void throwsOnIncompleteDataFramePayload() { + // Build header claiming 100-byte DATA payload but only provide 50 bytes + byte[] truncated = new byte[9 + 50]; + truncated[2] = 100; // length = 100 + truncated[3] = 0; // type = DATA + truncated[8] = 1; // streamId = 1 + var codec = new H2FrameCodec(new ByteArrayInputStream(truncated), new ByteArrayOutputStream(), 16384); + + assertThrows(IOException.class, codec::readFrame); + } + + // Helpers + private H2FrameCodec codec(ByteArrayOutputStream out) { + return new H2FrameCodec(new ByteArrayInputStream(new byte[0]), out, 16384); + } + + private H2FrameCodec.Frame decode(ByteArrayOutputStream out) throws IOException { + var c = new H2FrameCodec(new ByteArrayInputStream(out.toByteArray()), new ByteArrayOutputStream(), 16384); + return c.readFrame(); + } + + private H2FrameCodec.Frame decode(byte[] frame) throws IOException { + return new H2FrameCodec(new ByteArrayInputStream(frame), new ByteArrayOutputStream(), 16384).readFrame(); + } + + private byte[] buildFrame(int type, int flags, int streamId, byte[] payload) { + byte[] frame = new byte[9 + payload.length]; + frame[0] = (byte) ((payload.length >> 16) & 0xFF); + frame[1] = (byte) ((payload.length >> 8) & 0xFF); + frame[2] = (byte) (payload.length & 0xFF); + frame[3] = (byte) type; + frame[4] = (byte) flags; + frame[5] = (byte) ((streamId >> 24) & 0x7F); + frame[6] = (byte) ((streamId >> 16) & 0xFF); + frame[7] = (byte) ((streamId >> 8) & 0xFF); + frame[8] = (byte) (streamId & 0xFF); + System.arraycopy(payload, 0, frame, 9, payload.length); + return frame; + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameTestSuiteTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameTestSuiteTest.java new file mode 100644 index 000000000..84618dee4 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameTestSuiteTest.java @@ -0,0 +1,288 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_PADDED; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_PRIORITY; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * HTTP/2 frame codec test suite using test vectors from http2jp/http2-frame-test-case. + * + *

    The {@link #decodeFrame} test validates decoding against upstream JSON test vectors. + * The {@link #roundTripFrame} test validates encode-decode identity using our codec both ways. + * + * @see http2-frame-test-case + */ +class H2FrameTestSuiteTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final String[] FRAME_TYPES = { + "data", + "headers", + "priority", + "rst_stream", + "settings", + "push_promise", + "ping", + "goaway", + "window_update", + "continuation" + }; + + // Codec strips PADDED and PRIORITY flags after processing padding/priority fields + private static final int STRIPPED_FLAGS_MASK = ~(FLAG_PADDED | FLAG_PRIORITY); + + static Stream frameTestCases() throws IOException { + List args = new ArrayList<>(); + + for (String frameType : FRAME_TYPES) { + String[] files = {"normal.json", "error.json"}; + for (String file : files) { + String path = "http2-frame-test-case/" + frameType + "/" + file; + try (InputStream is = H2FrameTestSuiteTest.class.getClassLoader().getResourceAsStream(path)) { + if (is == null) { + // normal.json must exist; error.json is optional + if (file.equals("normal.json")) { + throw new IllegalStateException("Missing frame test resource: " + path); + } + continue; + } + + JsonNode root = MAPPER.readTree(is); + String wire = root.get("wire").asText(); + JsonNode error = root.get("error"); + JsonNode frame = root.get("frame"); + String description = root.has("description") ? root.get("description").asText() : file; + String testName = frameType + "/" + file + ": " + description; + + boolean expectError = error != null && !error.isNull(); + + if (!expectError && frame != null) { + int type = frame.get("type").asInt(); + int flags = frame.get("flags").asInt(); + int streamId = frame.get("stream_identifier").asInt(); + JsonNode framePayload = frame.get("frame_payload"); + + args.add(Arguments.of(testName, wire, false, type, flags, streamId, framePayload)); + } else if (expectError) { + args.add(Arguments.of(testName, wire, true, 0, 0, 0, null)); + } + } + } + } + + return args.stream(); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("frameTestCases") + void decodeFrame( + String description, + String wireHex, + boolean expectError, + int expectedType, + int expectedFlags, + int expectedStreamId, + JsonNode framePayload + ) throws IOException { + + byte[] wireBytes = hexToBytes(wireHex); + H2FrameCodec codec = new H2FrameCodec(new ByteArrayInputStream(wireBytes), new ByteArrayOutputStream(), 16384); + + if (expectError) { + assertThrows(IOException.class, codec::readFrame, "Expected error for: " + description); + } else { + H2FrameCodec.Frame frame = codec.readFrame(); + + assertNotNull(frame, "Frame should not be null for: " + description); + assertEquals(expectedType, frame.type(), "Type mismatch for: " + description); + assertEquals(expectedFlags & STRIPPED_FLAGS_MASK, + frame.flags() & STRIPPED_FLAGS_MASK, + "Flags mismatch for: " + description); + assertEquals(expectedStreamId, frame.streamId(), "Stream ID mismatch for: " + description); + + verifyPayload(frame, framePayload, description); + } + } + + static Stream roundTripTestCases() throws IOException { + List args = new ArrayList<>(); + + for (String frameType : FRAME_TYPES) { + String path = "http2-frame-test-case/" + frameType + "/normal.json"; + try (InputStream is = H2FrameTestSuiteTest.class.getClassLoader().getResourceAsStream(path)) { + if (is == null) { + throw new IllegalStateException("Missing frame test resource: " + path); + } + + JsonNode root = MAPPER.readTree(is); + String wire = root.get("wire").asText(); + String description = root.has("description") ? root.get("description").asText() : "normal"; + args.add(Arguments.of(frameType + ": " + description, wire)); + } + } + + return args.stream(); + } + + @ParameterizedTest(name = "roundtrip {0}") + @MethodSource("roundTripTestCases") + void roundTripFrame(String description, String wireHex) throws IOException { + byte[] wireBytes = hexToBytes(wireHex); + var decodeCodec = new H2FrameCodec(new ByteArrayInputStream(wireBytes), new ByteArrayOutputStream(), 16384); + H2FrameCodec.Frame original = decodeCodec.readFrame(); + + // Re-encode + var encodeOut = new ByteArrayOutputStream(); + var encodeCodec = new H2FrameCodec(new ByteArrayInputStream(new byte[0]), encodeOut, 16384); + encodeCodec.writeFrame( + original.type(), + original.flags(), + original.streamId(), + original.payload(), + 0, + original.payloadLength()); + encodeCodec.flush(); + + // Decode again + var redecodeCodec = new H2FrameCodec(new ByteArrayInputStream(encodeOut.toByteArray()), + new ByteArrayOutputStream(), + 16384); + H2FrameCodec.Frame roundTripped = redecodeCodec.readFrame(); + + // Verify matches + assertEquals(original.type(), roundTripped.type(), "Type mismatch after round-trip: " + description); + assertEquals(original.flags(), roundTripped.flags(), "Flags mismatch after round-trip: " + description); + assertEquals(original.streamId(), + roundTripped.streamId(), + "StreamId mismatch after round-trip: " + description); + assertEquals(original.payloadLength(), + roundTripped.payloadLength(), + "Length mismatch after round-trip: " + description); + + byte[] origPayload = new byte[original.payloadLength()]; + byte[] rtPayload = new byte[roundTripped.payloadLength()]; + System.arraycopy(original.payload(), 0, origPayload, 0, original.payloadLength()); + System.arraycopy(roundTripped.payload(), 0, rtPayload, 0, roundTripped.payloadLength()); + assertArrayEquals(origPayload, rtPayload, "Payload mismatch after round-trip: " + description); + } + + private void verifyPayload(H2FrameCodec.Frame frame, JsonNode payload, String description) throws IOException { + if (payload == null) { + return; + } + + switch (frame.type()) { + case 0 -> verifyDataPayload(frame, payload, description); // DATA + case 3 -> verifyRstStreamPayload(frame, payload, description); // RST_STREAM + case 4 -> verifySettingsPayload(frame, payload, description); // SETTINGS + case 6 -> verifyPingPayload(frame, payload, description); // PING + case 7 -> verifyGoawayPayload(frame, payload, description); // GOAWAY + case 8 -> verifyWindowUpdatePayload(frame, payload, description); // WINDOW_UPDATE + default -> { + // HEADERS, CONTINUATION, etc. have HPACK-encoded payloads + } + } + } + + private void verifyDataPayload(H2FrameCodec.Frame frame, JsonNode payload, String description) { + if (payload.has("data")) { + String expectedData = payload.get("data").asText(); + String actualData = new String(frame.payload(), 0, frame.payloadLength(), StandardCharsets.UTF_8); + assertEquals(expectedData, actualData, "DATA payload mismatch for: " + description); + } + } + + private void verifyRstStreamPayload(H2FrameCodec.Frame frame, JsonNode payload, String description) + throws IOException { + if (payload.has("error_code")) { + int expectedErrorCode = payload.get("error_code").asInt(); + int actualErrorCode = frame.parseRstStream(); + assertEquals(expectedErrorCode, actualErrorCode, "RST_STREAM error_code mismatch for: " + description); + } + } + + private void verifySettingsPayload(H2FrameCodec.Frame frame, JsonNode payload, String description) + throws IOException { + if (payload.has("settings")) { + int[] settings = frame.parseSettings(); + JsonNode expectedSettings = payload.get("settings"); + assertEquals(expectedSettings.size() * 2, + settings.length, + "SETTINGS count mismatch for: " + description); + for (int i = 0; i < expectedSettings.size(); i++) { + JsonNode pair = expectedSettings.get(i); + assertEquals(pair.get(0).asInt(), + settings[i * 2], + "SETTINGS id mismatch at " + i + " for: " + description); + assertEquals(pair.get(1).asInt(), + settings[i * 2 + 1], + "SETTINGS value mismatch at " + i + " for: " + description); + } + } + } + + private void verifyPingPayload(H2FrameCodec.Frame frame, JsonNode payload, String description) { + if (payload.has("opaque_data")) { + String expectedStr = payload.get("opaque_data").asText(); + byte[] expected = expectedStr.getBytes(StandardCharsets.US_ASCII); + byte[] actual = new byte[frame.payloadLength()]; + System.arraycopy(frame.payload(), 0, actual, 0, frame.payloadLength()); + assertArrayEquals(expected, actual, "PING opaque_data mismatch for: " + description); + } + } + + private void verifyGoawayPayload(H2FrameCodec.Frame frame, JsonNode payload, String description) + throws IOException { + int[] goaway = frame.parseGoaway(); + if (payload.has("last_stream_id")) { + assertEquals(payload.get("last_stream_id").asInt(), + goaway[0], + "GOAWAY last_stream_id mismatch for: " + description); + } + if (payload.has("error_code")) { + assertEquals(payload.get("error_code").asInt(), + goaway[1], + "GOAWAY error_code mismatch for: " + description); + } + } + + private void verifyWindowUpdatePayload(H2FrameCodec.Frame frame, JsonNode payload, String description) + throws IOException { + if (payload.has("window_size_increment")) { + int expected = payload.get("window_size_increment").asInt(); + int actual = frame.parseWindowUpdate(); + assertEquals(expected, actual, "WINDOW_UPDATE increment mismatch for: " + description); + } + } + + private static byte[] hexToBytes(String hex) { + int len = hex.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16)); + } + return data; + } +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/LICENSE b/http/http-client/src/test/resources/http2-frame-test-case/LICENSE new file mode 100644 index 000000000..148a84fb2 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/LICENSE @@ -0,0 +1,22 @@ +The test vectors in this directory are from the http2-frame-test-case project: +https://github.com/http2jp/http2-frame-test-case + +Copyright (c) 2014 HTTP/2 Japan Community + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/http/http-client/src/test/resources/http2-frame-test-case/continuation/header.json b/http/http-client/src/test/resources/http2-frame-test-case/continuation/header.json new file mode 100644 index 000000000..294eaf11c --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/continuation/header.json @@ -0,0 +1,14 @@ +{ + "error": null, + "wire": "00000D090000000032746869732069732064756D6D79", + "frame": { + "length": 13, + "frame_payload": { + "header_block_fragment": "this is dummy" + }, + "flags": 0, + "stream_identifier": 50, + "type": 9 + }, + "description": "normal continuation frame without header block fragment" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/continuation/normal.json b/http/http-client/src/test/resources/http2-frame-test-case/continuation/normal.json new file mode 100644 index 000000000..011b66149 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/continuation/normal.json @@ -0,0 +1,14 @@ +{ + "error": null, + "wire": "000000090000000032", + "frame": { + "length": 0, + "frame_payload": { + "header_block_fragment": "" + }, + "flags": 0, + "stream_identifier": 50, + "type": 9 + }, + "description": "normal continuation frame without header block fragment" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/data/normal.json b/http/http-client/src/test/resources/http2-frame-test-case/data/normal.json new file mode 100644 index 000000000..9ed3be97b --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/data/normal.json @@ -0,0 +1,16 @@ +{ + "error": null, + "wire": "0000140008000000020648656C6C6F2C20776F726C6421486F77647921", + "frame": { + "length": 20, + "frame_payload": { + "data": "Hello, world!", + "padding_length": 6, + "padding": "Howdy!" + }, + "flags": 8, + "stream_identifier": 2, + "type": 0 + }, + "description": "normal data frame" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/data-frame-padding.json b/http/http-client/src/test/resources/http2-frame-test-case/error/data-frame-padding.json new file mode 100644 index 000000000..89e86abdd --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/data-frame-padding.json @@ -0,0 +1,8 @@ +{ + "error": [ + 1 + ], + "wire": "00000400080000000104AAAAAA", + "frame": null, + "description": "data frame with invalid amount of padding" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/data-frame-size.json b/http/http-client/src/test/resources/http2-frame-test-case/error/data-frame-size.json new file mode 100644 index 000000000..67b572386 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/data-frame-size.json @@ -0,0 +1,8 @@ +{ + "error": [ + 6 + ], + "wire": "0080000008000000020648656C6C6F2C20776F726C6421686F77647921", + "frame": null, + "description": "data frame with frame size error" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/data-frame-stream.json b/http/http-client/src/test/resources/http2-frame-test-case/error/data-frame-stream.json new file mode 100644 index 000000000..6dbec2abf --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/data-frame-stream.json @@ -0,0 +1,8 @@ +{ + "error": [ + 1 + ], + "wire": "000001000000000000AA", + "frame": null, + "description": "data frame with invalid stream identifier" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/goaway-frame-size.json b/http/http-client/src/test/resources/http2-frame-test-case/error/goaway-frame-size.json new file mode 100644 index 000000000..6de0725ab --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/goaway-frame-size.json @@ -0,0 +1,8 @@ +{ + "error": [ + 6 + ], + "wire": "00000407000000000000000002", + "frame": null, + "description": "goaway frame of an invalid length" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/goaway-frame-stream.json b/http/http-client/src/test/resources/http2-frame-test-case/error/goaway-frame-stream.json new file mode 100644 index 000000000..275f8f1e2 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/goaway-frame-stream.json @@ -0,0 +1,8 @@ +{ + "error": [ + 1 + ], + "wire": "0000080700000000010000000200000003", + "frame": null, + "description": "goaway frame with invalid stream identifier" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/headers-frame-padding.json b/http/http-client/src/test/resources/http2-frame-test-case/error/headers-frame-padding.json new file mode 100644 index 000000000..f64abc8b7 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/headers-frame-padding.json @@ -0,0 +1,8 @@ +{ + "error": [ + 1 + ], + "wire": "00000401080000000104AAAAAA", + "frame": null, + "description": "headers frame with invalid amount of padding" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/headers-frame-stream.json b/http/http-client/src/test/resources/http2-frame-test-case/error/headers-frame-stream.json new file mode 100644 index 000000000..f8ac654f0 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/headers-frame-stream.json @@ -0,0 +1,8 @@ +{ + "error": [ + 1 + ], + "wire": "000001010000000000AA", + "frame": null, + "description": "headers frame with invalid stream identifier" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/ping-frame-size.json b/http/http-client/src/test/resources/http2-frame-test-case/error/ping-frame-size.json new file mode 100644 index 000000000..19aa4a502 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/ping-frame-size.json @@ -0,0 +1,8 @@ +{ + "error": [ + 6 + ], + "wire": "000004060000000000AAAAAAAA", + "frame": null, + "description": "ping frame of an invalid length" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/ping-frame-stream.json b/http/http-client/src/test/resources/http2-frame-test-case/error/ping-frame-stream.json new file mode 100644 index 000000000..f5db832e1 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/ping-frame-stream.json @@ -0,0 +1,8 @@ +{ + "error": [ + 1 + ], + "wire": "000008060100000001AAAAAAAAAAAAAAAA", + "frame": null, + "description": "ping frame with invalid stream identifier" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/priority-frame-size.json b/http/http-client/src/test/resources/http2-frame-test-case/error/priority-frame-size.json new file mode 100644 index 000000000..f89725b9a --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/priority-frame-size.json @@ -0,0 +1,8 @@ +{ + "error": [ + 6 + ], + "wire": "00000802000000000280000001FFAAAAAA", + "frame": null, + "description": "priority frame of an invalid length" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/priority-frame-stream.json b/http/http-client/src/test/resources/http2-frame-test-case/error/priority-frame-stream.json new file mode 100644 index 000000000..f8a77b229 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/priority-frame-stream.json @@ -0,0 +1,8 @@ +{ + "error": [ + 1 + ], + "wire": "000005020000000000AAAAAAAABB", + "frame": null, + "description": "priority frame with invalid stream identifier" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-padding.json b/http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-padding.json new file mode 100644 index 000000000..538ecb633 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-padding.json @@ -0,0 +1,9 @@ +{ + "error": [ + 1, + 6 + ], + "wire": "00000405080000000104AAAAAA", + "frame": null, + "description": "push_promise frame with invalid amount of padding" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-promised_stream-odd.json b/http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-promised_stream-odd.json new file mode 100644 index 000000000..c275e78ac --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-promised_stream-odd.json @@ -0,0 +1,8 @@ +{ + "error": [ + 1 + ], + "wire": "00000405000000000100000001", + "frame": null, + "description": "push_promise frame with invalid (odd-numbered) promised stream identifier" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-promised_stream-zero.json b/http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-promised_stream-zero.json new file mode 100644 index 000000000..f256c6b9c --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-promised_stream-zero.json @@ -0,0 +1,8 @@ +{ + "error": [ + 1 + ], + "wire": "00000405000000000100000000", + "frame": null, + "description": "push_promise frame with invalid (zero) promised stream identifier" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-stream.json b/http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-stream.json new file mode 100644 index 000000000..fa6c918a4 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/push_promise-frame-stream.json @@ -0,0 +1,8 @@ +{ + "error": [ + 1 + ], + "wire": "00000405000000000077777777", + "frame": null, + "description": "push_promise frame with invalid stream identifier" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/rst_stream-frame-size.json b/http/http-client/src/test/resources/http2-frame-test-case/error/rst_stream-frame-size.json new file mode 100644 index 000000000..9834a6800 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/rst_stream-frame-size.json @@ -0,0 +1,8 @@ +{ + "error": [ + 6 + ], + "wire": "000008030000000002AAAAAAAABBBBBBBB", + "frame": null, + "description": "rst_stream frame of an invalid length" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/rst_stream-frame-stream.json b/http/http-client/src/test/resources/http2-frame-test-case/error/rst_stream-frame-stream.json new file mode 100644 index 000000000..4c65625bf --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/rst_stream-frame-stream.json @@ -0,0 +1,8 @@ +{ + "error": [ + 1 + ], + "wire": "000004030000000000AAAAAAAA", + "frame": null, + "description": "rst_stream frame with invalid stream identifier" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/settings-frame-ack-size.json b/http/http-client/src/test/resources/http2-frame-test-case/error/settings-frame-ack-size.json new file mode 100644 index 000000000..21b104fe2 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/settings-frame-ack-size.json @@ -0,0 +1,8 @@ +{ + "error": [ + 6 + ], + "wire": "000006040100000000AAAABBBBBBBB", + "frame": null, + "description": "settings ack frame of an invalid length" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/settings-frame-size.json b/http/http-client/src/test/resources/http2-frame-test-case/error/settings-frame-size.json new file mode 100644 index 000000000..72a134e74 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/settings-frame-size.json @@ -0,0 +1,8 @@ +{ + "error": [ + 6 + ], + "wire": "000008040000000000AAAABBBBBBBBCCCC", + "frame": null, + "description": "settings frame of an invalid length" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/settings-frame-stream.json b/http/http-client/src/test/resources/http2-frame-test-case/error/settings-frame-stream.json new file mode 100644 index 000000000..74b50c629 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/settings-frame-stream.json @@ -0,0 +1,8 @@ +{ + "error": [ + 1 + ], + "wire": "000006040000000001AAAABBBBBBBB", + "frame": null, + "description": "settings frame with invalid stream identifier" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/window_update-frame-increment.json b/http/http-client/src/test/resources/http2-frame-test-case/error/window_update-frame-increment.json new file mode 100644 index 000000000..818e2770f --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/window_update-frame-increment.json @@ -0,0 +1,8 @@ +{ + "error": [ + 1 + ], + "wire": "00000408000000000100000000", + "frame": null, + "description": "window_update frame with invalid window size increment" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/error/window_update-frame-size.json b/http/http-client/src/test/resources/http2-frame-test-case/error/window_update-frame-size.json new file mode 100644 index 000000000..69ea953eb --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/error/window_update-frame-size.json @@ -0,0 +1,8 @@ +{ + "error": [ + 6 + ], + "wire": "0000020800000000015566", + "frame": null, + "description": "window_update frame of an invalid length" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/goaway/normal.json b/http/http-client/src/test/resources/http2-frame-test-case/goaway/normal.json new file mode 100644 index 000000000..f3b0f3023 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/goaway/normal.json @@ -0,0 +1,16 @@ +{ + "error": null, + "wire": "0000170700000000000000001E00000009687061636B2069732062726F6B656E", + "frame": { + "length": 23, + "frame_payload": { + "error_code": 9, + "additional_debug_data": "hpack is broken", + "last_stream_id": 30 + }, + "flags": 0, + "stream_identifier": 0, + "type": 7 + }, + "description": "normal goaway frame" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/headers/normal.json b/http/http-client/src/test/resources/http2-frame-test-case/headers/normal.json new file mode 100644 index 000000000..399d173c4 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/headers/normal.json @@ -0,0 +1,19 @@ +{ + "error": null, + "wire": "00000D010400000001746869732069732064756D6D79", + "frame": { + "length": 13, + "frame_payload": { + "stream_dependency": null, + "weight": null, + "header_block_fragment": "this is dummy", + "padding_length": null, + "exclusive": null, + "padding": null + }, + "flags": 4, + "stream_identifier": 1, + "type": 1 + }, + "description": "normal headers frame" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/headers/priority.json b/http/http-client/src/test/resources/http2-frame-test-case/headers/priority.json new file mode 100644 index 000000000..109920abc --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/headers/priority.json @@ -0,0 +1,19 @@ +{ + "error": null, + "wire": "000023012C00000003108000001409746869732069732064756D6D79546869732069732070616464696E672E", + "frame": { + "length": 35, + "frame_payload": { + "stream_dependency": 20, + "weight": 10, + "header_block_fragment": "this is dummy", + "padding_length": 16, + "exclusive": true, + "padding": "This is padding." + }, + "flags": 44, + "stream_identifier": 3, + "type": 1 + }, + "description": "normal headers frame including priority" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/ping/.gikeep b/http/http-client/src/test/resources/http2-frame-test-case/ping/.gikeep new file mode 100644 index 000000000..e69de29bb diff --git a/http/http-client/src/test/resources/http2-frame-test-case/ping/normal.json b/http/http-client/src/test/resources/http2-frame-test-case/ping/normal.json new file mode 100644 index 000000000..e881489b2 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/ping/normal.json @@ -0,0 +1,14 @@ +{ + "error": null, + "wire": "0000080600000000006465616462656566", + "frame": { + "length": 8, + "frame_payload": { + "opaque_data": "deadbeef" + }, + "flags": 0, + "stream_identifier": 0, + "type": 6 + }, + "description": "normal ping frame" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/priority/normal.json b/http/http-client/src/test/resources/http2-frame-test-case/priority/normal.json new file mode 100644 index 000000000..91592cec3 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/priority/normal.json @@ -0,0 +1,18 @@ +{ + "error": null, + "wire": "0000050200000000090000000B07", + "frame": { + "length": 5, + "frame_payload": { + "stream_dependency": 11, + "weight": 8, + "exclusive": false, + "padding_length": null, + "padding": null + }, + "flags": 0, + "stream_identifier": 9, + "type": 2 + }, + "description": "normal priority frame" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/push_promise/normal.json b/http/http-client/src/test/resources/http2-frame-test-case/push_promise/normal.json new file mode 100644 index 000000000..1dc9de63d --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/push_promise/normal.json @@ -0,0 +1,17 @@ +{ + "error": null, + "wire": "000018050C0000000A060000000C746869732069732064756D6D79486F77647921", + "frame": { + "length": 24, + "frame_payload": { + "header_block_fragment": "this is dummy", + "padding_length": 6, + "promised_stream_id": 12, + "padding": "Howdy!" + }, + "flags": 12, + "stream_identifier": 10, + "type": 5 + }, + "description": "normal push promise frame" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/rst_stream/normal.json b/http/http-client/src/test/resources/http2-frame-test-case/rst_stream/normal.json new file mode 100644 index 000000000..b9568753a --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/rst_stream/normal.json @@ -0,0 +1,14 @@ +{ + "error": null, + "wire": "00000403000000000500000008", + "frame": { + "length": 4, + "frame_payload": { + "error_code": 8 + }, + "flags": 0, + "stream_identifier": 5, + "type": 3 + }, + "description": "normal rst stream frame" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/settings/normal.json b/http/http-client/src/test/resources/http2-frame-test-case/settings/normal.json new file mode 100644 index 000000000..ae4d39a07 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/settings/normal.json @@ -0,0 +1,23 @@ +{ + "error": null, + "wire": "00000C040000000000000100002000000300001388", + "frame": { + "length": 12, + "frame_payload": { + "settings": [ + [ + 1, + 8192 + ], + [ + 3, + 5000 + ] + ] + }, + "flags": 0, + "stream_identifier": 0, + "type": 4 + }, + "description": "normal rst stream frame" +} diff --git a/http/http-client/src/test/resources/http2-frame-test-case/window_update/normal.json b/http/http-client/src/test/resources/http2-frame-test-case/window_update/normal.json new file mode 100644 index 000000000..68d181f19 --- /dev/null +++ b/http/http-client/src/test/resources/http2-frame-test-case/window_update/normal.json @@ -0,0 +1,14 @@ +{ + "error": null, + "wire": "000004080000000032000003E8", + "frame": { + "length": 4, + "frame_payload": { + "window_size_increment": 1000 + }, + "flags": 0, + "stream_identifier": 50, + "type": 8 + }, + "description": "normal window update frame" +} From 93f7defb3675d3f11d87ed834ce6bc864907ea93 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Fri, 5 Dec 2025 14:05:42 -0600 Subject: [PATCH 16/60] Fix spotbugs issues --- .../http/client/it/server/Http2ConnectionFrameHandler.java | 2 +- .../amazon/smithy/java/http/client/BenchmarkServer.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java index 103a9827e..60d5f988a 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java @@ -28,7 +28,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { "Received GOAWAY frame, error code: {}, last streamId: {}", goAwayFrame.errorCode(), goAwayFrame.lastStreamId()); - } else if (msg instanceof Http2SettingsAckFrame ackFrame) { + } else if (msg instanceof Http2SettingsAckFrame) { LOGGER.info(ctx.channel(), "Received settings ack frame"); } else { // Unknown connection-level frame diff --git a/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java index 25544dfbb..c9081bc56 100644 --- a/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java +++ b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java @@ -344,7 +344,10 @@ public static void main(String[] args) throws Exception { // Write port file File portFile = new File(portFilePath); - portFile.getParentFile().mkdirs(); + File parentDir = portFile.getParentFile(); + if (parentDir != null && !parentDir.exists() && !parentDir.mkdirs()) { + throw new IOException("Failed to create directory: " + parentDir); + } server.writePortFile(portFile); System.out.println("Port file written to: " + portFile.getAbsolutePath()); From a768f1989244e02ffc551ae1fd920f6e4c8d14cc Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Fri, 5 Dec 2025 14:19:13 -0600 Subject: [PATCH 17/60] Add H1Connection tests --- .../java/http/client/h1/H1Connection.java | 16 +- .../java/http/client/h1/H1ConnectionTest.java | 301 ++++++++++++++++++ 2 files changed, 309 insertions(+), 8 deletions(-) create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/H1ConnectionTest.java diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Connection.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Connection.java index 10558e99c..9c1cfcef5 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Connection.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Connection.java @@ -45,9 +45,8 @@ */ public final class H1Connection implements HttpConnection { /** - * The buffer used for parsing response start-line and each header line. This means that the path and query - * string can't exceed 8KB, and no one header can exceed 8KB (though we do impose a limit of 512 headers to guard - * against malformed responses). This per/line limit should be more than enough for well-formed response parsing. + * Buffer used for parsing the HTTP/1.x status line and each header line. + * This bounds any single response line to 8KB (status line or header line). */ static final int RESPONSE_LINE_BUFFER_SIZE = 8192; @@ -112,9 +111,8 @@ public HttpVersion httpVersion() { @Override public boolean isActive() { - // Fast path: just check volatile flags (no syscalls) - // Full socket health check happens in validateForReuse() when connection - // is retrieved from pool after being idle + // Cheap check used by the pool on hot paths. + // Full socket state validation is done in validateForReuse(). return active && keepAlive; } @@ -250,7 +248,9 @@ UnsyncBufferedOutputStream getOutputStream() { *

    Called by {@link H1Exchange} when errors occur during I/O. */ void markInactive() { - LOGGER.debug("Marking connection inactive to {}", route); - this.active = false; + if (active) { + LOGGER.debug("Marking connection inactive to {}", route); + this.active = false; + } } } diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/H1ConnectionTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/H1ConnectionTest.java new file mode 100644 index 000000000..63c161855 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/H1ConnectionTest.java @@ -0,0 +1,301 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.Route; + +class H1ConnectionTest { + + private static final Route TEST_ROUTE = Route.direct("https", "example.com", 443); + private static final Duration READ_TIMEOUT = Duration.ofSeconds(5); + + @Test + void createsConnectionSuccessfully() throws IOException { + var socket = new FakeSocket(""); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + + assertTrue(connection.isActive()); + assertEquals(HttpVersion.HTTP_1_1, connection.httpVersion()); + assertEquals(TEST_ROUTE, connection.route()); + } + + @Test + void createsExchangeSuccessfully() throws IOException { + var socket = new FakeSocket("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("https://example.com/test")) + .build(); + var exchange = connection.newExchange(request); + + assertNotNull(exchange); + exchange.close(); + } + + @Test + void throwsOnConcurrentExchange() throws IOException { + var socket = new FakeSocket("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("https://example.com/test")) + .build(); + + connection.newExchange(request); + + assertThrows(IOException.class, () -> connection.newExchange(request)); + } + + @Test + void throwsOnClosedConnection() throws IOException { + var socket = new FakeSocket(""); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("https://example.com/test")) + .build(); + + connection.close(); + + assertThrows(IOException.class, () -> connection.newExchange(request)); + } + + @Test + void isActiveReturnsFalseAfterClose() throws IOException { + var socket = new FakeSocket(""); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + connection.close(); + + assertFalse(connection.isActive()); + } + + @Test + void isActiveReturnsFalseWhenKeepAliveDisabled() throws IOException { + var socket = new FakeSocket(""); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + connection.setKeepAlive(false); + + assertFalse(connection.isActive()); + } + + @Test + void validateForReuseReturnsTrueForHealthyConnection() throws IOException { + var socket = new FakeSocket(""); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + + assertTrue(connection.validateForReuse()); + } + + @Test + void validateForReuseReturnsFalseWhenInactive() throws IOException { + var socket = new FakeSocket(""); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + connection.markInactive(); + + assertFalse(connection.validateForReuse()); + } + + @Test + void validateForReuseReturnsFalseWhenKeepAliveDisabled() throws IOException { + var socket = new FakeSocket(""); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + connection.setKeepAlive(false); + + assertFalse(connection.validateForReuse()); + } + + @Test + void validateForReuseReturnsFalseWhenSocketClosed() throws IOException { + var socket = new FakeSocket(""); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + socket.close(); + + assertFalse(connection.validateForReuse()); + assertFalse(connection.isActive()); + } + + @Test + void validateForReuseReturnsFalseWhenDataAvailableOnIdleConnection() throws IOException { + var socket = new FakeSocket("HTTP/1.1 200 OK\r\n"); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + + assertFalse(connection.validateForReuse()); + assertFalse(connection.isActive()); + } + + @Test + void validateForReuseReturnsFalseWhenAvailableThrows() throws IOException { + var socket = new FailingAvailableSocket(); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + + assertFalse(connection.validateForReuse()); + assertFalse(connection.isActive()); + } + + @Test + void sslSessionReturnsNullForPlainSocket() throws IOException { + var socket = new FakeSocket(""); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + + assertNull(connection.sslSession()); + } + + @Test + void negotiatedProtocolReturnsNullForPlainSocket() throws IOException { + var socket = new FakeSocket(""); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + + assertNull(connection.negotiatedProtocol()); + } + + @Test + void setAndGetSocketTimeout() throws IOException { + var socket = new FakeSocket(""); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + connection.setSocketTimeout(1000); + + assertEquals(1000, connection.getSocketTimeout()); + } + + @Test + void keepAliveDefaultsToTrue() throws IOException { + var socket = new FakeSocket(""); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + + assertTrue(connection.isKeepAlive()); + } + + @Test + void markInactiveSetsConnectionInactive() throws IOException { + var socket = new FakeSocket(""); + var connection = new H1Connection(socket, TEST_ROUTE, READ_TIMEOUT); + connection.markInactive(); + + assertFalse(connection.isActive()); + } + + @Test + void nullReadTimeoutDoesNotSetSocketTimeout() throws IOException { + var socket = new FakeSocket(""); + var connection = new H1Connection(socket, TEST_ROUTE, null); + + assertEquals(0, connection.getSocketTimeout()); + } + + @Test + void zeroReadTimeoutDoesNotSetSocketTimeout() throws IOException { + var socket = new FakeSocket(""); + var connection = new H1Connection(socket, TEST_ROUTE, Duration.ZERO); + + assertEquals(0, connection.getSocketTimeout()); + } + + static class FakeSocket extends Socket { + private final ByteArrayInputStream in; + private final ByteArrayOutputStream out; + private final InetAddress address; + private int soTimeout = 0; + private boolean closed = false; + + FakeSocket(String response) throws IOException { + this.in = new ByteArrayInputStream(response.getBytes(StandardCharsets.US_ASCII)); + this.out = new ByteArrayOutputStream(); + this.address = InetAddress.getByName("127.0.0.1"); + } + + @Override + public InputStream getInputStream() { + return in; + } + + @Override + public OutputStream getOutputStream() { + return out; + } + + @Override + public InetAddress getInetAddress() { + return address; + } + + @Override + public int getPort() { + return 443; + } + + @Override + public void setSoTimeout(int timeout) { + this.soTimeout = timeout; + } + + @Override + public int getSoTimeout() { + return soTimeout; + } + + @Override + public boolean isClosed() { + return closed; + } + + @Override + public boolean isInputShutdown() { + return closed; + } + + @Override + public boolean isOutputShutdown() { + return closed; + } + + @Override + public void close() { + closed = true; + } + } + + static final class FailingAvailableSocket extends FakeSocket { + FailingAvailableSocket() throws IOException { + super(""); + } + + @Override + public InputStream getInputStream() { + return new InputStream() { + @Override + public int read() { + return -1; + } + + @Override + public int available() throws IOException { + throw new IOException("boom"); + } + }; + } + } +} From 5e9150b6ed4869dd18b46928d178dab85ef861e7 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Fri, 5 Dec 2025 14:25:37 -0600 Subject: [PATCH 18/60] Add more tests for UnsyncBufferedInputStreamTest --- .../client/UnsyncBufferedInputStreamTest.java | 289 ++++++++++++++++++ 1 file changed, 289 insertions(+) diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStreamTest.java index 8ea3e723d..ebf2e44a7 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStreamTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStreamTest.java @@ -38,6 +38,115 @@ void readsIntoArray() throws IOException { assertArrayEquals(new byte[] {1, 2, 3}, buf); } + @Test + void readArrayDelegatesToReadWithOffsetAndLength() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + byte[] buf = new byte[5]; + assertEquals(3, stream.read(buf)); + assertArrayEquals(new byte[] {1, 2, 3, 0, 0}, buf); + } + + @Test + void readWithOffsetAndLength() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + byte[] buf = new byte[10]; + assertEquals(3, stream.read(buf, 2, 3)); + assertArrayEquals(new byte[] {0, 0, 1, 2, 3, 0, 0, 0, 0, 0}, buf); + } + + @Test + void readReturnsZeroWhenLenIsZero() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + assertEquals(0, stream.read(new byte[10], 0, 0)); + } + + @Test + void readThrowsOnNegativeOffset() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + assertThrows(IndexOutOfBoundsException.class, () -> stream.read(new byte[10], -1, 5)); + } + + @Test + void readThrowsOnNegativeLength() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + assertThrows(IndexOutOfBoundsException.class, () -> stream.read(new byte[10], 0, -1)); + } + + @Test + void readThrowsWhenLengthExceedsArrayBounds() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + assertThrows(IndexOutOfBoundsException.class, () -> stream.read(new byte[10], 5, 10)); + } + + @Test + void readBypassesBufferForLargeRequests() throws IOException { + var data = new byte[100]; + for (int i = 0; i < data.length; i++) { + data[i] = (byte) i; + } + var delegate = new ByteArrayInputStream(data); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + byte[] buf = new byte[100]; + assertEquals(100, stream.read(buf)); + assertArrayEquals(data, buf); + } + + @Test + void readDrainsBufferThenRefills() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + var stream = new UnsyncBufferedInputStream(delegate, 4); + + // First read fills buffer with [1,2,3,4], returns 3 + byte[] buf = new byte[3]; + assertEquals(3, stream.read(buf)); + assertArrayEquals(new byte[] {1, 2, 3}, buf); + + // Second read drains remaining [4], refills with [5,6,7,8], returns 3 + assertEquals(3, stream.read(buf)); + assertArrayEquals(new byte[] {4, 5, 6}, buf); + } + + @Test + void readReturnsMinusOneOnEmptyStream() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + assertEquals(-1, stream.read()); + assertEquals(-1, stream.read(new byte[10])); + } + + @Test + void readReturnsPartialDataThenMinusOne() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + byte[] buf = new byte[10]; + assertEquals(2, stream.read(buf)); + assertEquals(-1, stream.read(buf)); + } + + @Test + void readThrowsWhenClosed() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + stream.close(); + + assertThrows(IOException.class, () -> stream.read(new byte[10], 0, 5)); + } + @Test void skipsBytes() throws IOException { var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); @@ -47,6 +156,68 @@ void skipsBytes() throws IOException { assertEquals(3, stream.read()); } + @Test + void skipReturnsZeroForNonPositive() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + assertEquals(0, stream.skip(0)); + assertEquals(0, stream.skip(-5)); + } + + @Test + void skipThrowsWhenClosed() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + stream.close(); + + assertThrows(IOException.class, () -> stream.skip(1)); + } + + @Test + void skipDrainsBufferThenSkipsUnderlying() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + var stream = new UnsyncBufferedInputStream(delegate, 4); + + // Fill buffer first + stream.read(); + + // Skip more than buffer has (3 in buffer + some from underlying) + assertEquals(6, stream.skip(6)); + assertEquals(8, stream.read()); + } + + @Test + void availableReturnsBufferedPlusUnderlying() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + // Before any read, buffer is empty + assertEquals(5, stream.available()); + + // After read, buffer has data + stream.read(); + assertEquals(4, stream.available()); + } + + @Test + void availableThrowsWhenClosed() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + stream.close(); + + assertThrows(IOException.class, stream::available); + } + + @Test + void closeIsIdempotent() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + stream.close(); + stream.close(); // Should not throw + } + @Test void transfersToOutputStream() throws IOException { var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); @@ -57,6 +228,28 @@ void transfersToOutputStream() throws IOException { assertArrayEquals(new byte[] {1, 2, 3, 4, 5}, out.toByteArray()); } + @Test + void transferToDrainsBufferFirst() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + // Read one byte to fill buffer + assertEquals(1, stream.read()); + + var out = new ByteArrayOutputStream(); + assertEquals(4, stream.transferTo(out)); + assertArrayEquals(new byte[] {2, 3, 4, 5}, out.toByteArray()); + } + + @Test + void transferToThrowsWhenClosed() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + stream.close(); + + assertThrows(IOException.class, () -> stream.transferTo(new ByteArrayOutputStream())); + } + @Test void readLineReturnsLine() throws IOException { var data = "Hello\r\nWorld\n".getBytes(StandardCharsets.US_ASCII); @@ -65,12 +258,101 @@ void readLineReturnsLine() throws IOException { byte[] buf = new byte[64]; int len = stream.readLine(buf, 64); + assertEquals(5, len); + assertEquals("Hello", new String(buf, 0, len, StandardCharsets.US_ASCII)); + + len = stream.readLine(buf, 64); + assertEquals(5, len); + assertEquals("World", new String(buf, 0, len, StandardCharsets.US_ASCII)); + } + + @Test + void readLineHandlesCrOnly() throws IOException { + var data = "Hello\rWorld".getBytes(StandardCharsets.US_ASCII); + var delegate = new ByteArrayInputStream(data); + var stream = new UnsyncBufferedInputStream(delegate, 64); + + byte[] buf = new byte[64]; + int len = stream.readLine(buf, 64); + assertEquals(5, len); assertEquals("Hello", new String(buf, 0, len, StandardCharsets.US_ASCII)); len = stream.readLine(buf, 64); + assertEquals(5, len); assertEquals("World", new String(buf, 0, len, StandardCharsets.US_ASCII)); } + @Test + void readLineReturnsMinusOneOnEmptyStream() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {}); + var stream = new UnsyncBufferedInputStream(delegate, 64); + + assertEquals(-1, stream.readLine(new byte[64], 64)); + } + + @Test + void readLineReturnsDataWithoutTerminatorAtEof() throws IOException { + var data = "Hello".getBytes(StandardCharsets.US_ASCII); + var delegate = new ByteArrayInputStream(data); + var stream = new UnsyncBufferedInputStream(delegate, 64); + + byte[] buf = new byte[64]; + int len = stream.readLine(buf, 64); + assertEquals("Hello", new String(buf, 0, len, StandardCharsets.US_ASCII)); + } + + @Test + void readLineThrowsWhenExceedsMaxLength() throws IOException { + var data = "HelloWorld\r\n".getBytes(StandardCharsets.US_ASCII); + var delegate = new ByteArrayInputStream(data); + var stream = new UnsyncBufferedInputStream(delegate, 64); + + assertThrows(IOException.class, () -> stream.readLine(new byte[64], 5)); + } + + @Test + void readLineThrowsWhenExceedsBufferSize() throws IOException { + var data = "HelloWorld\r\n".getBytes(StandardCharsets.US_ASCII); + var delegate = new ByteArrayInputStream(data); + var stream = new UnsyncBufferedInputStream(delegate, 64); + + assertThrows(IOException.class, () -> stream.readLine(new byte[5], 64)); + } + + @Test + void readLineThrowsWhenClosed() throws IOException { + var delegate = new ByteArrayInputStream("Hello\r\n".getBytes(StandardCharsets.US_ASCII)); + var stream = new UnsyncBufferedInputStream(delegate, 64); + stream.close(); + + assertThrows(IOException.class, () -> stream.readLine(new byte[64], 64)); + } + + @Test + void readLineHandlesEmptyLine() throws IOException { + var data = "\r\nHello\r\n".getBytes(StandardCharsets.US_ASCII); + var delegate = new ByteArrayInputStream(data); + var stream = new UnsyncBufferedInputStream(delegate, 64); + + byte[] buf = new byte[64]; + int len = stream.readLine(buf, 64); + assertEquals(0, len); + + len = stream.readLine(buf, 64); + assertEquals("Hello", new String(buf, 0, len, StandardCharsets.US_ASCII)); + } + + @Test + void readLineSpansMultipleBufferFills() throws IOException { + var data = "HelloWorld\r\n".getBytes(StandardCharsets.US_ASCII); + var delegate = new ByteArrayInputStream(data); + var stream = new UnsyncBufferedInputStream(delegate, 4); // Small buffer + + byte[] buf = new byte[64]; + int len = stream.readLine(buf, 64); + assertEquals("HelloWorld", new String(buf, 0, len, StandardCharsets.US_ASCII)); + } + @Test void throwsAfterClose() throws IOException { var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); @@ -86,4 +368,11 @@ void throwsOnInvalidBufferSize() { assertThrows(IllegalArgumentException.class, () -> new UnsyncBufferedInputStream(delegate, 0)); } + + @Test + void throwsOnNegativeBufferSize() { + var delegate = new ByteArrayInputStream(new byte[] {}); + assertThrows(IllegalArgumentException.class, + () -> new UnsyncBufferedInputStream(delegate, -1)); + } } From 0acc921d240e6f68e4335d35ec0f5894d91652e2 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Fri, 5 Dec 2025 14:39:28 -0600 Subject: [PATCH 19/60] Improve ManagedHttpExchangeTest coverage --- .../http/client/ManagedHttpExchangeTest.java | 125 +++++++++++++++++- 1 file changed, 118 insertions(+), 7 deletions(-) diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/ManagedHttpExchangeTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/ManagedHttpExchangeTest.java index c075c5808..17d7eb4dd 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/ManagedHttpExchangeTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/ManagedHttpExchangeTest.java @@ -47,7 +47,7 @@ public void release(HttpConnection conn) { exchange.responseBody().close(); - assertTrue(released.get()); + assertTrue(released.get(), "Connection should be released on successful close"); } @Test @@ -70,7 +70,7 @@ public void evict(HttpConnection conn, boolean close) { exchange.close(); } catch (IOException ignored) {} - assertTrue(evicted.get()); + assertTrue(evicted.get(), "Connection should be evicted on error"); } @Test @@ -86,7 +86,7 @@ public void close() { exchange.responseBody().close(); - assertTrue(closed.get()); + assertTrue(closed.get(), "Closing response body should close the exchange"); } @Test @@ -104,7 +104,7 @@ public void release(HttpConnection conn) { exchange.close(); exchange.close(); - assertEquals(1, releaseCount.get()); + assertEquals(1, releaseCount.get(), "Connection should only be released once"); } @Test @@ -125,8 +125,10 @@ public HttpResponse interceptResponse( }; var exchange = createExchange(new TestConnectionPool(), List.of(interceptor)); - assertEquals(999, exchange.responseStatusCode()); - assertEquals("intercepted", new String(exchange.responseBody().readAllBytes())); + assertEquals(999, exchange.responseStatusCode(), "Status code should be from intercepted response"); + assertEquals("intercepted", + new String(exchange.responseBody().readAllBytes()), + "Body should be from intercepted response"); } @Test @@ -136,7 +138,7 @@ void responseBodyReturnsSameStream() throws IOException { var body1 = exchange.responseBody(); var body2 = exchange.responseBody(); - assertSame(body1, body2); + assertSame(body1, body2, "responseBody() should return the same stream instance"); } @Test @@ -305,6 +307,115 @@ public void release(HttpConnection conn) { assertTrue(released.get(), "Connection should still be released"); } + @Test + void onErrorInterceptorCanRecoverFromException() throws IOException { + var interceptor = new HttpInterceptor() { + @Override + public HttpResponse interceptResponse( + HttpClient client, + HttpRequest request, + Context context, + HttpResponse response + ) throws IOException { + throw new IOException("interceptor failed"); + } + + @Override + public HttpResponse onError( + HttpClient client, + HttpRequest request, + Context context, + IOException error + ) { + return HttpResponse.builder() + .statusCode(503) + .body(DataStream.ofString("recovered")) + .build(); + } + }; + var exchange = createExchange(new TestConnectionPool(), List.of(interceptor)); + + assertEquals(503, exchange.responseStatusCode(), "Status code should be from recovered response"); + assertEquals("recovered", + new String(exchange.responseBody().readAllBytes()), + "Body should be from recovered response"); + } + + @Test + void responseVersionReturnsFromDelegate() throws IOException { + var delegate = new TestHttpExchange() { + @Override + public HttpVersion responseVersion() { + return HttpVersion.HTTP_2; + } + }; + var exchange = createExchange(new TestConnectionPool(), List.of(), delegate); + + assertEquals(HttpVersion.HTTP_2, + exchange.responseVersion(), + "Response version should come from delegate when no interceptor"); + } + + @Test + void responseVersionReturnsFromInterceptedResponse() throws IOException { + var interceptor = new HttpInterceptor() { + @Override + public HttpResponse interceptResponse( + HttpClient client, + HttpRequest request, + Context context, + HttpResponse response + ) { + return HttpResponse.builder() + .statusCode(200) + .httpVersion(HttpVersion.HTTP_2) + .body(DataStream.ofString("intercepted")) + .build(); + } + }; + var exchange = createExchange(new TestConnectionPool(), List.of(interceptor)); + + assertEquals(HttpVersion.HTTP_2, + exchange.responseVersion(), + "Response version should come from intercepted response"); + } + + @Test + void interceptorThatDoesNotReplaceUsesOriginalBody() throws IOException { + var originalBodyRead = new AtomicBoolean(false); + var delegate = new TestHttpExchange() { + @Override + public InputStream responseBody() { + return new ByteArrayInputStream("original-body".getBytes()) { + @Override + public int read(byte[] b, int off, int len) { + originalBodyRead.set(true); + return super.read(b, off, len); + } + }; + } + }; + + // Pass-through interceptor that returns null (no replacement) + var interceptor = new HttpInterceptor() { + @Override + public HttpResponse interceptResponse( + HttpClient client, + HttpRequest request, + Context context, + HttpResponse response + ) { + return null; // No replacement + } + }; + var exchange = createExchange(new TestConnectionPool(), List.of(interceptor), delegate); + + String body = new String(exchange.responseBody().readAllBytes()); + + assertEquals("original-body", body, "Body should be from original response"); + assertTrue(originalBodyRead.get(), "Original body stream should be read"); + } + private ManagedHttpExchange createExchange(ConnectionPool pool, List interceptors) { return createExchange(pool, interceptors, new TestHttpExchange()); } From 0b3eea73e764440b1f6018bf7ce5879fd664b639 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Fri, 5 Dec 2025 15:25:35 -0600 Subject: [PATCH 20/60] Add more test coverage, including DefaultHttpClient --- .../http/client/BoundedInputStreamTest.java | 21 + .../http/client/DefaultHttpClientTest.java | 783 ++++++++++++++++++ .../UnsyncBufferedOutputStreamTest.java | 48 ++ 3 files changed, 852 insertions(+) create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/DefaultHttpClientTest.java diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/BoundedInputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/BoundedInputStreamTest.java index 3af64fca8..c8c831aa2 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/BoundedInputStreamTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/BoundedInputStreamTest.java @@ -78,4 +78,25 @@ void throwsOnPrematureEof() { } }); } + + @Test + void throwsOnPrematureEofInBulkRead() { + var delegate = new ByteArrayInputStream(new byte[] {1, 2}); + var stream = new BoundedInputStream(delegate, 5); + + assertThrows(IOException.class, () -> { + byte[] buf = new byte[10]; + while (stream.read(buf, 0, 10) != -1) { + // drain + } + }); + } + + @Test + void throwsOnPrematureEofDuringClose() { + var delegate = new ByteArrayInputStream(new byte[] {1, 2}); + var stream = new BoundedInputStream(delegate, 5); + + assertThrows(IOException.class, stream::close); + } } diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DefaultHttpClientTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DefaultHttpClientTest.java new file mode 100644 index 000000000..c5a092dab --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/DefaultHttpClientTest.java @@ -0,0 +1,783 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import javax.net.ssl.SSLSession; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.context.Context; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.ConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpConnection; +import software.amazon.smithy.java.http.client.connection.Route; +import software.amazon.smithy.java.io.datastream.DataStream; + +class DefaultHttpClientTest { + + @Test + void sendReturnsResponse() throws IOException { + var pool = new TestConnectionPool(); + try (var client = HttpClient.builder().connectionPool(pool).build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + + var response = client.send(request); + + assertEquals(200, response.statusCode(), "Should return status from exchange"); + assertEquals("test-body", + new String(response.body().asInputStream().readAllBytes()), + "Should return body from exchange"); + } + } + + @Test + void sendWritesRequestBody() throws IOException { + var bodyWritten = new AtomicReference(); + var pool = new TestConnectionPool() { + @Override + protected HttpExchange createExchange() { + return new TestHttpExchange() { + @Override + public OutputStream requestBody() { + return new OutputStream() { + private final StringBuilder sb = new StringBuilder(); + + @Override + public void write(int b) { + sb.append((char) b); + } + + @Override + public void close() { + bodyWritten.set(sb.toString()); + } + }; + } + }; + } + }; + try (var client = HttpClient.builder().connectionPool(pool).build()) { + var request = HttpRequest.builder() + .method("POST") + .uri(URI.create("http://example.com/test")) + .body(DataStream.ofString("request-body")) + .build(); + + client.send(request); + + assertEquals("request-body", bodyWritten.get(), "Request body should be written"); + } + } + + @Test + void beforeRequestInterceptorModifiesRequest() throws IOException { + var capturedUri = new AtomicReference(); + var pool = new TestConnectionPool() { + @Override + protected HttpExchange createExchange() { + return new TestHttpExchange() { + @Override + public HttpRequest request() { + return null; + } + }; + } + + @Override + public HttpConnection acquire(Route route) { + capturedUri.set(URI.create(route.scheme() + "://" + route.host() + ":" + route.port())); + return super.acquire(route); + } + }; + var interceptor = new HttpInterceptor() { + @Override + public HttpRequest beforeRequest(HttpClient client, HttpRequest request, Context context) { + return request.toBuilder() + .uri(URI.create("http://modified.com/path")) + .build(); + } + }; + try (var client = HttpClient.builder().connectionPool(pool).addInterceptor(interceptor).build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://original.com/test")) + .build(); + + client.send(request); + + assertEquals("http://modified.com:80", + capturedUri.get().toString(), + "Request URI should be modified by interceptor"); + } + } + + @Test + void preemptRequestReturnsWithoutNetworkCall() throws IOException { + var networkCalled = new AtomicBoolean(false); + var pool = new TestConnectionPool() { + @Override + public HttpConnection acquire(Route route) { + networkCalled.set(true); + return super.acquire(route); + } + }; + var interceptor = new HttpInterceptor() { + @Override + public HttpResponse preemptRequest(HttpClient client, HttpRequest request, Context context) { + return HttpResponse.builder() + .statusCode(304) + .body(DataStream.ofString("cached")) + .build(); + } + }; + try (var client = HttpClient.builder().connectionPool(pool).addInterceptor(interceptor).build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + + var response = client.send(request); + + assertEquals(304, response.statusCode(), "Should return preempted response"); + assertEquals("cached", + new String(response.body().asInputStream().readAllBytes()), + "Should return preempted body"); + assertFalse(networkCalled.get(), "Should not make network call when preempted"); + } + } + + @Test + void preemptedResponseCanBeReplacedByInterceptResponse() throws IOException { + var pool = new TestConnectionPool(); + var interceptor = new HttpInterceptor() { + @Override + public HttpResponse preemptRequest(HttpClient client, HttpRequest request, Context context) { + return HttpResponse.builder() + .statusCode(304) + .body(DataStream.ofString("cached")) + .build(); + } + + @Override + public HttpResponse interceptResponse( + HttpClient client, + HttpRequest request, + Context context, + HttpResponse response + ) { + return HttpResponse.builder() + .statusCode(200) + .body(DataStream.ofString("modified-cached")) + .build(); + } + }; + try (var client = HttpClient.builder().connectionPool(pool).addInterceptor(interceptor).build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + + var response = client.send(request); + + assertEquals(200, response.statusCode(), "Should return intercepted status"); + assertEquals("modified-cached", + new String(response.body().asInputStream().readAllBytes()), + "Should return intercepted body"); + } + } + + @Test + void preemptRequestFailsWithoutRecovery() throws IOException { + var pool = new TestConnectionPool(); + var interceptor = new HttpInterceptor() { + @Override + public HttpResponse preemptRequest(HttpClient client, HttpRequest request, Context context) + throws IOException { + throw new IOException("preempt failed"); + } + }; + try (var client = HttpClient.builder().connectionPool(pool).addInterceptor(interceptor).build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + + var ex = assertThrows(IOException.class, () -> client.send(request)); + assertEquals("preempt failed", ex.getMessage(), "Should propagate preempt exception"); + } + } + + @Test + void preemptRequestFailsAndRecovers() throws IOException { + var pool = new TestConnectionPool(); + var interceptor = new HttpInterceptor() { + @Override + public HttpResponse preemptRequest(HttpClient client, HttpRequest request, Context context) { + return HttpResponse.builder() + .statusCode(200) + .body(DataStream.ofString("preempted")) + .build(); + } + + @Override + public HttpResponse interceptResponse( + HttpClient client, + HttpRequest request, + Context context, + HttpResponse response + ) throws IOException { + throw new IOException("intercept failed"); + } + + @Override + public HttpResponse onError( + HttpClient client, + HttpRequest request, + Context context, + IOException error + ) { + return HttpResponse.builder() + .statusCode(503) + .body(DataStream.ofString("recovered")) + .build(); + } + }; + try (var client = HttpClient.builder().connectionPool(pool).addInterceptor(interceptor).build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + + var response = client.send(request); + + assertEquals(503, response.statusCode(), "Should return recovered response"); + assertEquals("recovered", + new String(response.body().asInputStream().readAllBytes()), + "Should return recovered body"); + } + } + + @Test + void interceptResponseCanReplaceResponse() throws IOException { + var pool = new TestConnectionPool(); + var interceptor = new HttpInterceptor() { + @Override + public HttpResponse interceptResponse( + HttpClient client, + HttpRequest request, + Context context, + HttpResponse response + ) { + return HttpResponse.builder() + .statusCode(999) + .body(DataStream.ofString("intercepted")) + .build(); + } + }; + try (var client = HttpClient.builder().connectionPool(pool).addInterceptor(interceptor).build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + + var response = client.send(request); + + assertEquals(999, response.statusCode(), "Should return intercepted status"); + assertEquals("intercepted", + new String(response.body().asInputStream().readAllBytes()), + "Should return intercepted body"); + } + } + + @Test + void onErrorCanRecoverFromNetworkFailure() throws IOException { + var pool = new TestConnectionPool() { + @Override + public HttpConnection acquire(Route route) { + return new TestConnection() { + @Override + public HttpExchange newExchange(HttpRequest request) throws IOException { + throw new IOException("network failure"); + } + }; + } + }; + var interceptor = new HttpInterceptor() { + @Override + public HttpResponse onError( + HttpClient client, + HttpRequest request, + Context context, + IOException error + ) { + return HttpResponse.builder() + .statusCode(503) + .body(DataStream.ofString("fallback")) + .build(); + } + }; + try (var client = HttpClient.builder().connectionPool(pool).addInterceptor(interceptor).build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + + var response = client.send(request); + + assertEquals(503, response.statusCode(), "Should return recovery response"); + assertEquals("fallback", + new String(response.body().asInputStream().readAllBytes()), + "Should return recovery body"); + } + } + + @Test + void interceptorsExecuteInCorrectOrder() throws IOException { + var order = new AtomicInteger(0); + var beforeA = new AtomicInteger(); + var beforeB = new AtomicInteger(); + var responseA = new AtomicInteger(); + var responseB = new AtomicInteger(); + + var interceptorA = new HttpInterceptor() { + @Override + public HttpRequest beforeRequest(HttpClient client, HttpRequest request, Context context) { + beforeA.set(order.incrementAndGet()); + return request; + } + + @Override + public HttpResponse interceptResponse( + HttpClient client, + HttpRequest request, + Context context, + HttpResponse response + ) { + responseA.set(order.incrementAndGet()); + return response; + } + }; + var interceptorB = new HttpInterceptor() { + @Override + public HttpRequest beforeRequest(HttpClient client, HttpRequest request, Context context) { + beforeB.set(order.incrementAndGet()); + return request; + } + + @Override + public HttpResponse interceptResponse( + HttpClient client, + HttpRequest request, + Context context, + HttpResponse response + ) { + responseB.set(order.incrementAndGet()); + return response; + } + }; + + var pool = new TestConnectionPool(); + try (var client = HttpClient.builder() + .connectionPool(pool) + .addInterceptor(interceptorA) + .addInterceptor(interceptorB) + .build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + + client.send(request); + + assertEquals(1, beforeA.get(), "beforeRequest A should be first"); + assertEquals(2, beforeB.get(), "beforeRequest B should be second"); + assertEquals(3, responseB.get(), "interceptResponse B should be third (reverse order)"); + assertEquals(4, responseA.get(), "interceptResponse A should be fourth (reverse order)"); + } + } + + @Test + void requestTimeoutThrowsOnTimeout() throws IOException { + var pool = new TestConnectionPool() { + @Override + protected HttpExchange createExchange() { + return new TestHttpExchange() { + @Override + public int responseStatusCode() throws IOException { + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + throw new IOException("interrupted", e); + } + return 200; + } + }; + } + }; + try (var client = HttpClient.builder() + .connectionPool(pool) + .requestTimeout(Duration.ofMillis(50)) + .build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + + var ex = assertThrows(IOException.class, () -> client.send(request)); + assertTrue(ex.getMessage().contains("exceeded request timeout"), + "Should indicate timeout: " + ex.getMessage()); + } + } + + @Test + void requestTimeoutSucceedsWhenFastEnough() throws IOException { + var pool = new TestConnectionPool(); + try (var client = HttpClient.builder() + .connectionPool(pool) + .requestTimeout(Duration.ofSeconds(5)) + .build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + + var response = client.send(request); + + assertEquals(200, response.statusCode(), "Should complete within timeout"); + } + } + + @Test + void newExchangeReturnsExchange() throws IOException { + var pool = new TestConnectionPool(); + try (var client = HttpClient.builder().connectionPool(pool).build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + + var exchange = client.newExchange(request); + + assertNotNull(exchange, "Should return exchange"); + assertEquals(200, exchange.responseStatusCode(), "Should return status from delegate"); + exchange.close(); + } + } + + @Test + void proxySelectorsAreUsed() throws IOException { + var proxyUsed = new AtomicBoolean(false); + var pool = new TestConnectionPool() { + @Override + public HttpConnection acquire(Route route) { + if (route.usesProxy()) { + proxyUsed.set(true); + } + return super.acquire(route); + } + }; + var proxy = new ProxyConfiguration(URI.create("http://proxy.example.com:8080"), + ProxyConfiguration.ProxyType.HTTP); + try (var client = HttpClient.builder() + .connectionPool(pool) + .proxy(proxy) + .build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + + client.send(request); + + assertTrue(proxyUsed.get(), "Proxy should be used"); + } + } + + @Test + void proxyFailoverSucceedsOnSecondProxy() throws IOException { + var attemptedProxies = new AtomicInteger(0); + var pool = new TestConnectionPool() { + @Override + public HttpConnection acquire(Route route) { + attemptedProxies.incrementAndGet(); + if (route.proxy() != null && route.proxy().port() == 8080) { + return new TestConnection() { + @Override + public HttpExchange newExchange(HttpRequest request) throws IOException { + throw new IOException("first proxy failed"); + } + }; + } + return super.acquire(route); + } + }; + var proxy1 = new ProxyConfiguration(URI.create("http://proxy1.example.com:8080"), + ProxyConfiguration.ProxyType.HTTP); + var proxy2 = new ProxyConfiguration(URI.create("http://proxy2.example.com:9090"), + ProxyConfiguration.ProxyType.HTTP); + var connectFailedCalled = new AtomicBoolean(false); + var selector = new ProxySelector() { + @Override + public List select(URI target, Context context) { + return List.of(proxy1, proxy2); + } + + @Override + public void connectFailed(URI target, Context context, ProxyConfiguration proxy, IOException cause) { + connectFailedCalled.set(true); + } + }; + try (var client = HttpClient.builder() + .connectionPool(pool) + .proxySelector(selector) + .build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + + var response = client.send(request); + + assertEquals(200, response.statusCode(), "Should succeed via second proxy"); + assertEquals(2, attemptedProxies.get(), "Should have tried both proxies"); + assertTrue(connectFailedCalled.get(), "connectFailed should be called for first proxy"); + } + } + + @Test + void proxyFailoverThrowsWhenAllProxiesFail() throws IOException { + var attemptedProxies = new AtomicInteger(0); + var pool = new TestConnectionPool() { + @Override + public HttpConnection acquire(Route route) { + attemptedProxies.incrementAndGet(); + return new TestConnection() { + @Override + public HttpExchange newExchange(HttpRequest request) throws IOException { + throw new IOException("proxy " + attemptedProxies.get() + " failed"); + } + }; + } + }; + var proxy1 = new ProxyConfiguration(URI.create("http://proxy1.example.com:8080"), + ProxyConfiguration.ProxyType.HTTP); + var proxy2 = new ProxyConfiguration(URI.create("http://proxy2.example.com:9090"), + ProxyConfiguration.ProxyType.HTTP); + var selector = ProxySelector.of(proxy1, proxy2); + try (var client = HttpClient.builder() + .connectionPool(pool) + .proxySelector(selector) + .build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + + var ex = assertThrows(IOException.class, () -> client.send(request)); + assertEquals("proxy 2 failed", ex.getMessage(), "Should throw last proxy's exception"); + assertEquals(2, attemptedProxies.get(), "Should have tried both proxies"); + } + } + + @Test + void connectionEvictedOnExchangeCreationFailure() throws IOException { + var evicted = new AtomicBoolean(false); + var pool = new TestConnectionPool() { + @Override + public HttpConnection acquire(Route route) { + return new TestConnection() { + @Override + public HttpExchange newExchange(HttpRequest request) throws IOException { + throw new IOException("exchange creation failed"); + } + }; + } + + @Override + public void evict(HttpConnection connection, boolean close) { + evicted.set(true); + } + }; + try (var client = HttpClient.builder().connectionPool(pool).build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + + assertThrows(IOException.class, () -> client.send(request)); + assertTrue(evicted.get(), "Connection should be evicted on exchange creation failure"); + } + } + + @Test + void requestOptionsInterceptorsAreApplied() throws IOException { + var clientInterceptorCalled = new AtomicBoolean(false); + var requestInterceptorCalled = new AtomicBoolean(false); + + var clientInterceptor = new HttpInterceptor() { + @Override + public HttpRequest beforeRequest(HttpClient client, HttpRequest request, Context context) { + clientInterceptorCalled.set(true); + return request; + } + }; + var requestInterceptor = new HttpInterceptor() { + @Override + public HttpRequest beforeRequest(HttpClient client, HttpRequest request, Context context) { + requestInterceptorCalled.set(true); + return request; + } + }; + + var pool = new TestConnectionPool(); + try (var client = HttpClient.builder() + .connectionPool(pool) + .addInterceptor(clientInterceptor) + .build()) { + var request = HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + var options = RequestOptions.builder() + .addInterceptor(requestInterceptor) + .build(); + + client.send(request, options); + + assertTrue(clientInterceptorCalled.get(), "Client interceptor should be called"); + assertTrue(requestInterceptorCalled.get(), "Request interceptor should be called"); + } + } + + // Test fixtures + + private static class TestConnectionPool implements ConnectionPool { + @Override + public HttpConnection acquire(Route route) { + return new TestConnection() { + @Override + public HttpExchange newExchange(HttpRequest request) { + return createExchange(); + } + }; + } + + protected HttpExchange createExchange() { + return new TestHttpExchange(); + } + + @Override + public void release(HttpConnection connection) {} + + @Override + public void evict(HttpConnection connection, boolean close) {} + + @Override + public void close() {} + + @Override + public void shutdown(Duration timeout) {} + } + + private static class TestConnection implements HttpConnection { + @Override + public HttpExchange newExchange(HttpRequest request) throws IOException { + return new TestHttpExchange(); + } + + @Override + public HttpVersion httpVersion() { + return HttpVersion.HTTP_1_1; + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public Route route() { + return Route.direct("http", "example.com", 80); + } + + @Override + public void close() {} + + @Override + public SSLSession sslSession() { + return null; + } + + @Override + public String negotiatedProtocol() { + return null; + } + + @Override + public boolean validateForReuse() { + return true; + } + } + + private static class TestHttpExchange implements HttpExchange { + @Override + public HttpRequest request() { + return HttpRequest.builder() + .method("GET") + .uri(URI.create("http://example.com/test")) + .build(); + } + + @Override + public OutputStream requestBody() { + return OutputStream.nullOutputStream(); + } + + @Override + public InputStream responseBody() { + return new ByteArrayInputStream("test-body".getBytes()); + } + + @Override + public HttpHeaders responseHeaders() { + return HttpHeaders.of(Map.of()); + } + + @Override + public int responseStatusCode() throws IOException { + return 200; + } + + @Override + public HttpVersion responseVersion() { + return HttpVersion.HTTP_1_1; + } + + @Override + public void close() {} + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStreamTest.java index e98dc9c67..7211e2637 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStreamTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStreamTest.java @@ -28,6 +28,33 @@ void writesSingleBytes() throws IOException { assertArrayEquals(new byte[] {1, 2, 3}, delegate.toByteArray()); } + @Test + void singleByteWriteFlushesWhenBufferFull() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new UnsyncBufferedOutputStream(delegate, 4); + + stream.write(1); + stream.write(2); + stream.write(3); + stream.write(4); + // Buffer is now full, next write should flush + stream.write(5); + stream.flush(); + + assertArrayEquals(new byte[] {1, 2, 3, 4, 5}, delegate.toByteArray()); + } + + @Test + void zeroLengthWriteDoesNothing() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new UnsyncBufferedOutputStream(delegate, 8); + + stream.write(new byte[] {1, 2, 3}, 0, 0); + stream.flush(); + + assertEquals(0, delegate.size()); + } + @Test void writesArray() throws IOException { var delegate = new ByteArrayOutputStream(); @@ -50,6 +77,18 @@ void writesAsciiString() throws IOException { assertEquals("Hello", delegate.toString()); } + @Test + void writeAsciiFlushesWhenBufferFills() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new UnsyncBufferedOutputStream(delegate, 4); + + // String longer than buffer forces mid-string flush + stream.writeAscii("HelloWorld"); + stream.flush(); + + assertEquals("HelloWorld", delegate.toString()); + } + @Test void flushesOnClose() throws IOException { var delegate = new ByteArrayOutputStream(); @@ -70,6 +109,15 @@ void throwsAfterClose() throws IOException { assertThrows(IOException.class, () -> stream.write(1)); } + @Test + void flushThrowsAfterClose() throws IOException { + var delegate = new ByteArrayOutputStream(); + var stream = new UnsyncBufferedOutputStream(delegate, 8); + stream.close(); + + assertThrows(IOException.class, stream::flush); + } + @Test void throwsOnInvalidBufferSize() { var delegate = new ByteArrayOutputStream(); From efb7c35197e825b51139b2deb4220fe5f69a0ebd Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Fri, 5 Dec 2025 15:40:27 -0600 Subject: [PATCH 21/60] Add H1ConnectionManager tests --- .../connection/H1ConnectionManager.java | 33 +- .../client/connection/HttpConnectionPool.java | 9 +- .../connection/H1ConnectionManagerTest.java | 312 ++++++++++++++++++ 3 files changed, 336 insertions(+), 18 deletions(-) create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManagerTest.java diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java index 020847cbf..b37255960 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java @@ -14,6 +14,7 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.IntFunction; +import software.amazon.smithy.java.logging.InternalLogger; /** * Manages HTTP/1.1 connection pooling. @@ -23,6 +24,8 @@ */ final class H1ConnectionManager { + private static final InternalLogger LOGGER = InternalLogger.getLogger(H1ConnectionManager.class); + // Skip expensive socket validation for connections idle < 1 second private static final long VALIDATION_THRESHOLD_NANOS = 1_000_000_000L; @@ -46,10 +49,17 @@ PooledConnection tryAcquire(Route route, IntFunction poolFactory) { PooledConnection pooled; while ((pooled = hostPool.poll()) != null) { if (validateConnection(pooled)) { + LOGGER.debug("Reusing pooled connection to {}", route); return pooled; } - // Connection failed validation - caller should close and release permit - return new PooledConnection(pooled.connection, -1); // -1 signals invalid + + // Connection failed validation - close it and try next + LOGGER.debug("Closing invalid pooled connection to {}", route); + try { + pooled.connection.close(); + } catch (IOException e) { + LOGGER.debug("Error closing invalid connection to {}: {}", route, e.getMessage()); + } } return null; } @@ -68,6 +78,7 @@ void ensurePool(Route route, int maxConnections) { */ boolean release(Route route, HttpConnection connection, boolean poolClosed) { if (!connection.isActive() || poolClosed) { + LOGGER.debug("Not pooling inactive connection to {} (poolClosed={})", route, poolClosed); return false; } @@ -77,10 +88,14 @@ boolean release(Route route, HttpConnection connection, boolean poolClosed) { } try { - return hostPool.offer( - new PooledConnection(connection, System.nanoTime()), - 10, - TimeUnit.MILLISECONDS); + var conn = new PooledConnection(connection, System.nanoTime()); + boolean pooled = hostPool.offer(conn, 10, TimeUnit.MILLISECONDS); + if (pooled) { + LOGGER.debug("Released h1 connection to pool for {}", route); + } else { + LOGGER.debug("h1 pool full, not pooling connection to {}", route); + } + return pooled; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; @@ -141,11 +156,7 @@ private boolean validateConnection(PooledConnection pooled) { /** * A pooled connection with idle timestamp. */ - record PooledConnection(HttpConnection connection, long idleSinceNanos) { - boolean isValid() { - return idleSinceNanos >= 0; - } - } + record PooledConnection(HttpConnection connection, long idleSinceNanos) {} /** * Per-route connection pool using blocking deque (LIFO). diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java index 5a7767b99..566dff5c1 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java @@ -221,13 +221,8 @@ private HttpConnection acquireH1(Route route) throws IOException { ignored -> new H1ConnectionManager.HostPool(maxConns)); if (pooled != null) { - if (pooled.isValid()) { - notifyAcquire(pooled.connection(), true); - return pooled.connection(); - } - // Connection failed validation: it's unhealthy or stale - closeConnection(pooled.connection()); - connectionPermits.release(); + notifyAcquire(pooled.connection(), true); + return pooled.connection(); } // No valid pooled connection available, so block on global capacity with timeout. diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManagerTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManagerTest.java new file mode 100644 index 000000000..4989cf5a7 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManagerTest.java @@ -0,0 +1,312 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import javax.net.ssl.SSLSession; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpExchange; + +class H1ConnectionManagerTest { + + private static final Route TEST_ROUTE = Route.direct("http", "example.com", 80); + private static final long MAX_IDLE_NANOS = TimeUnit.SECONDS.toNanos(30); + + @Test + void tryAcquireReturnsNullWhenPoolEmpty() { + var manager = new H1ConnectionManager(MAX_IDLE_NANOS); + + var result = manager.tryAcquire(TEST_ROUTE, ignored -> new H1ConnectionManager.HostPool(10)); + + assertNull(result, "Should return null when pool is empty"); + } + + @Test + void tryAcquireReturnsPooledConnection() { + var manager = new H1ConnectionManager(MAX_IDLE_NANOS); + var connection = new TestConnection(); + + manager.release(TEST_ROUTE, connection, false); + // Need to ensure pool exists first + manager.ensurePool(TEST_ROUTE, 10); + manager.release(TEST_ROUTE, connection, false); + + var result = manager.tryAcquire(TEST_ROUTE, ignored -> new H1ConnectionManager.HostPool(10)); + + assertNotNull(result, "Should return pooled connection"); + assertEquals(connection, result.connection(), "Should return the same connection"); + } + + @Test + void tryAcquireRejectsOverlyIdleConnections() throws Exception { + var manager = new H1ConnectionManager(TimeUnit.MILLISECONDS.toNanos(10)); // 10ms max idle + var closeCalled = new AtomicBoolean(false); + var connection = new TestConnection() { + @Override + public void close() { + closeCalled.set(true); + } + }; + + manager.ensurePool(TEST_ROUTE, 10); + manager.release(TEST_ROUTE, connection, false); + + Thread.sleep(50); // Wait longer than max idle time + + var result = manager.tryAcquire(TEST_ROUTE, ignored -> new H1ConnectionManager.HostPool(10)); + + assertNull(result, "Should not return overly idle connection"); + assertTrue(closeCalled.get(), "Overly idle connection should be closed"); + } + + @Test + void tryAcquireValidatesConnectionIdleLongerThanThreshold() throws Exception { + var manager = new H1ConnectionManager(TimeUnit.SECONDS.toNanos(30)); // 30s max idle + var validateCalled = new AtomicBoolean(false); + var connection = new TestConnection() { + @Override + public boolean validateForReuse() { + validateCalled.set(true); + return true; + } + }; + + manager.ensurePool(TEST_ROUTE, 10); + manager.release(TEST_ROUTE, connection, false); + + Thread.sleep(1100); // Wait > 1 second (VALIDATION_THRESHOLD_NANOS) + + var result = manager.tryAcquire(TEST_ROUTE, ignored -> new H1ConnectionManager.HostPool(10)); + + assertNotNull(result, "Should return validated connection"); + assertTrue(validateCalled.get(), "validateForReuse should be called for connections idle > 1s"); + } + + @Test + void tryAcquireSkipsInvalidConnections() { + var manager = new H1ConnectionManager(MAX_IDLE_NANOS); + var invalidConnection = new TestConnection() { + @Override + public boolean isActive() { + return false; + } + }; + var validConnection = new TestConnection(); + + manager.ensurePool(TEST_ROUTE, 10); + manager.release(TEST_ROUTE, validConnection, false); + manager.release(TEST_ROUTE, invalidConnection, false); + + // Should skip invalid and return valid (LIFO order, so invalid is tried first) + var result = manager.tryAcquire(TEST_ROUTE, ignored -> new H1ConnectionManager.HostPool(10)); + + assertNotNull(result, "Should return valid connection"); + assertEquals(validConnection, result.connection(), "Should skip invalid and return valid"); + } + + @Test + void tryAcquireClosesInvalidConnections() { + var manager = new H1ConnectionManager(MAX_IDLE_NANOS); + var closeCalled = new AtomicBoolean(false); + var active = new AtomicBoolean(true); + var invalidConnection = new TestConnection() { + @Override + public boolean isActive() { + return active.get(); + } + + @Override + public void close() { + closeCalled.set(true); + } + }; + + manager.ensurePool(TEST_ROUTE, 10); + manager.release(TEST_ROUTE, invalidConnection, false); + // Connection becomes inactive after being pooled + active.set(false); + + manager.tryAcquire(TEST_ROUTE, ignored -> new H1ConnectionManager.HostPool(10)); + + assertTrue(closeCalled.get(), "Invalid connection should be closed"); + } + + @Test + void releaseReturnsFalseWhenConnectionInactive() { + var manager = new H1ConnectionManager(MAX_IDLE_NANOS); + var inactiveConnection = new TestConnection() { + @Override + public boolean isActive() { + return false; + } + }; + + manager.ensurePool(TEST_ROUTE, 10); + boolean released = manager.release(TEST_ROUTE, inactiveConnection, false); + + assertFalse(released, "Should not release inactive connection"); + } + + @Test + void releaseReturnsFalseWhenPoolClosed() { + var manager = new H1ConnectionManager(MAX_IDLE_NANOS); + var connection = new TestConnection(); + + manager.ensurePool(TEST_ROUTE, 10); + boolean released = manager.release(TEST_ROUTE, connection, true); + + assertFalse(released, "Should not release when pool is closed"); + } + + @Test + void releaseReturnsFalseWhenNoPoolExists() { + var manager = new H1ConnectionManager(MAX_IDLE_NANOS); + var connection = new TestConnection(); + + boolean released = manager.release(TEST_ROUTE, connection, false); + + assertFalse(released, "Should not release when no pool exists"); + } + + @Test + void removeRemovesConnectionFromPool() { + var manager = new H1ConnectionManager(MAX_IDLE_NANOS); + var connection = new TestConnection(); + + manager.ensurePool(TEST_ROUTE, 10); + manager.release(TEST_ROUTE, connection, false); + manager.remove(TEST_ROUTE, connection); + + var result = manager.tryAcquire(TEST_ROUTE, ignored -> new H1ConnectionManager.HostPool(10)); + + assertNull(result, "Connection should be removed from pool"); + } + + @Test + void cleanupIdleRemovesExpiredConnections() throws Exception { + var manager = new H1ConnectionManager(1); // 1 nanosecond max idle + var connection = new TestConnection(); + + manager.ensurePool(TEST_ROUTE, 10); + manager.release(TEST_ROUTE, connection, false); + + Thread.sleep(10); // Ensure connection is expired + + var removed = new AtomicInteger(0); + int count = manager.cleanupIdle((conn, reason) -> removed.incrementAndGet()); + + assertEquals(1, count, "Should remove 1 expired connection"); + assertEquals(1, removed.get(), "Callback should be called once"); + } + + @Test + void cleanupIdleRemovesUnhealthyConnections() { + var manager = new H1ConnectionManager(MAX_IDLE_NANOS); + var unhealthyConnection = new TestConnection() { + private boolean active = true; + + @Override + public boolean isActive() { + return active; + } + + void setInactive() { + active = false; + } + }; + + manager.ensurePool(TEST_ROUTE, 10); + manager.release(TEST_ROUTE, unhealthyConnection, false); + unhealthyConnection.setInactive(); + + var reasons = new ArrayList(); + manager.cleanupIdle((conn, reason) -> reasons.add(reason)); + + assertEquals(1, reasons.size(), "Should remove unhealthy connection"); + assertEquals(CloseReason.UNEXPECTED_CLOSE, reasons.get(0), "Reason should be UNEXPECTED_CLOSE"); + } + + @Test + void closeAllClosesAllConnections() { + var manager = new H1ConnectionManager(MAX_IDLE_NANOS); + var closedConnections = new AtomicInteger(0); + var connection1 = new TestConnection() { + @Override + public void close() { + closedConnections.incrementAndGet(); + } + }; + var connection2 = new TestConnection() { + @Override + public void close() { + closedConnections.incrementAndGet(); + } + }; + + manager.ensurePool(TEST_ROUTE, 10); + manager.release(TEST_ROUTE, connection1, false); + manager.release(TEST_ROUTE, connection2, false); + + var exceptions = new ArrayList(); + manager.closeAll(exceptions, conn -> {}); + + assertEquals(2, closedConnections.get(), "All connections should be closed"); + assertTrue(exceptions.isEmpty(), "No exceptions expected"); + } + + // Test connection implementation + private static class TestConnection implements HttpConnection { + @Override + public HttpExchange newExchange(HttpRequest request) { + return null; + } + + @Override + public HttpVersion httpVersion() { + return HttpVersion.HTTP_1_1; + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public Route route() { + return TEST_ROUTE; + } + + @Override + public void close() {} + + @Override + public SSLSession sslSession() { + return null; + } + + @Override + public String negotiatedProtocol() { + return null; + } + + @Override + public boolean validateForReuse() { + return true; + } + } +} From 1e6b272dd997d0d8ee0ed399e6d1d76c91d244ef Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Fri, 5 Dec 2025 18:29:39 -0600 Subject: [PATCH 22/60] Make some bench improvements --- http/http-client/build.gradle.kts | 2 +- .../client/VirtualThreadScalingBenchmark.java | 192 ++++++++++-------- .../java/http/client/BenchmarkServer.java | 19 +- .../connection/H2ConnectionManager.java | 78 +++++-- .../client/connection/HttpConnectionPool.java | 73 +++---- 5 files changed, 208 insertions(+), 156 deletions(-) diff --git a/http/http-client/build.gradle.kts b/http/http-client/build.gradle.kts index 9e68282cf..c76974f2f 100644 --- a/http/http-client/build.gradle.kts +++ b/http/http-client/build.gradle.kts @@ -131,7 +131,7 @@ val stopBenchmarkServer by tasks.registering { // Configure JMH jmh { - includes = listOf(".*smithyH2c.*") + includes = listOf(".*nettyH2c.*") warmupIterations = 2 iterations = 3 diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java index 24dfa5e00..e749fff6b 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java @@ -27,18 +27,21 @@ import io.netty.handler.codec.http2.Http2StreamChannelBootstrap; import io.netty.handler.codec.http2.Http2StreamFrame; import java.io.IOException; +import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.time.Duration; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLContext; @@ -104,7 +107,7 @@ @State(Scope.Benchmark) public class VirtualThreadScalingBenchmark { - @Param({"5000"}) + @Param({"10000"}) private int concurrency; // Server URL - use jmhWithServer task or set manually @@ -128,10 +131,10 @@ public class VirtualThreadScalingBenchmark { private Http2Client helidonClientH2; // Netty HTTP/2 client (raw, no abstractions) - // Note: Netty performs better with 1 connection (606K) vs multiple (425K with 100 connections) - // This is because Netty's event-loop model multiplexes efficiently on a single connection private EventLoopGroup nettyEventLoopGroup; - private Channel nettyH2cChannel; + private Bootstrap nettyBootstrap; + private String nettyH2cHost; + private int nettyH2cPort; // Server URLs private String h1BaseUrl; @@ -248,7 +251,7 @@ public void setup() throws Exception { // ===== Netty HTTP/2 Client (raw, no abstractions) ===== nettyEventLoopGroup = new NioEventLoopGroup(); - Bootstrap nettyBootstrap = new Bootstrap(); + nettyBootstrap = new Bootstrap(); nettyBootstrap.group(nettyEventLoopGroup) .channel(NioSocketChannel.class) .option(ChannelOption.SO_KEEPALIVE, true) @@ -274,10 +277,9 @@ protected void channelRead0( } }); - // Connect to h2c server (single connection - Netty performs better this way) - String h2cHost = h2cBaseUrl.replaceAll("https?://", "").replaceAll(":\\d+.*", ""); - int h2cPort = 18081; - nettyH2cChannel = nettyBootstrap.connect(new InetSocketAddress(h2cHost, h2cPort)).sync().channel(); + // Store host/port for connection during benchmark (not pre-connected) + nettyH2cHost = h2cBaseUrl.replaceAll("https?://", "").replaceAll(":\\d+.*", ""); + nettyH2cPort = 18081; } private SSLContext createTrustAllSslContext() throws Exception { @@ -327,10 +329,7 @@ public void teardown() throws Exception { helidonClientH2.closeResource(); } - // Close Netty client - if (nettyH2cChannel != null) { - nettyH2cChannel.close().sync(); - } + // Close Netty event loop group if (nettyEventLoopGroup != null) { nettyEventLoopGroup.shutdownGracefully().sync(); } @@ -495,7 +494,7 @@ public void smithyH2c(RequestCounter counter) throws InterruptedException { try { while (running.get()) { try (var res = smithyClientH2c.send(request)) { - res.body().asInputStream().readAllBytes(); + res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); requests.incrementAndGet(); } } @@ -567,103 +566,134 @@ public void helidonH2c(RequestCounter counter) throws InterruptedException { } /** - * Raw Netty HTTP/2 client throughput. - * - *

    Uses Netty's HTTP/2 multiplexing with a single connection and event-loop threads. - * Netty performs better with 1 connection (606K) vs multiple (425K with 100 connections) - * because its event-loop model multiplexes efficiently on a single connection. + * Raw Netty HTTP/2 client throughput with single connection. */ @Benchmark @Threads(1) public void nettyH2c(RequestCounter counter) throws InterruptedException { + runNettyH2c(1, counter); + } + + /** + * Raw Netty HTTP/2 client throughput with pooled connections. + */ + @Benchmark + @Threads(1) + public void nettyH2cPooled(RequestCounter counter) throws InterruptedException { + runNettyH2c(3, counter); + } + + private void runNettyH2c(int numConnections, RequestCounter counter) throws InterruptedException { var requests = new AtomicLong(); var errors = new AtomicLong(); var firstError = new AtomicReference(); var running = new AtomicBoolean(true); var activeTasks = new AtomicLong(concurrency); - // Create stream bootstrap for multiplexing on single connection - Http2StreamChannelBootstrap streamBootstrap = new Http2StreamChannelBootstrap(nettyH2cChannel); - - // Handler that opens a new stream, sends request, and repeats on response - Runnable[] makeRequest = new Runnable[1]; - makeRequest[0] = () -> { - if (!running.get()) { - activeTasks.decrementAndGet(); - return; + // Connect to h2c server during benchmark + List channels = new ArrayList<>(); + try { + for (int i = 0; i < numConnections; i++) { + channels.add(nettyBootstrap.connect(new InetSocketAddress(nettyH2cHost, nettyH2cPort)).sync().channel()); } + } catch (Exception e) { + counter.errors = 1; + return; + } - streamBootstrap.open().addListener(future -> { - if (!future.isSuccess()) { - errors.incrementAndGet(); - firstError.compareAndSet(null, future.cause()); - if (!running.get()) { - activeTasks.decrementAndGet(); - } else { - // Retry - makeRequest[0].run(); - } + try { + // Create stream bootstraps for each connection + List streamBootstraps = channels.stream() + .map(Http2StreamChannelBootstrap::new) + .toList(); + + // Pre-create headers + DefaultHttp2Headers headers = new DefaultHttp2Headers(); + headers.method("GET"); + headers.path("/get"); + headers.scheme("http"); + headers.authority("localhost:18081"); + + // Handler that opens a new stream, sends request, and repeats on response + Runnable[] makeRequest = new Runnable[1]; + var connectionIndex = new AtomicInteger(0); + makeRequest[0] = () -> { + if (!running.get()) { + activeTasks.decrementAndGet(); return; } - Http2StreamChannel streamChannel = (Http2StreamChannel) future.get(); - streamChannel.pipeline().addLast(new SimpleChannelInboundHandler() { - @Override - protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame frame) { - boolean endStream = false; - if (frame instanceof Http2HeadersFrame headersFrame) { - endStream = headersFrame.isEndStream(); - } else if (frame instanceof io.netty.handler.codec.http2.Http2DataFrame dataFrame) { - endStream = dataFrame.isEndStream(); + // Round-robin across connections + int idx = connectionIndex.getAndIncrement() % streamBootstraps.size(); + Http2StreamChannelBootstrap streamBootstrap = streamBootstraps.get(idx); + + streamBootstrap.open().addListener(future -> { + if (!future.isSuccess()) { + errors.incrementAndGet(); + firstError.compareAndSet(null, future.cause()); + if (!running.get()) { + activeTasks.decrementAndGet(); + } else { + makeRequest[0].run(); } + return; + } - if (endStream) { - requests.incrementAndGet(); + Http2StreamChannel streamChannel = (Http2StreamChannel) future.get(); + streamChannel.pipeline().addLast(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame frame) { + if (frame instanceof io.netty.handler.codec.http2.Http2DataFrame dataFrame) { + dataFrame.content().readableBytes(); + } + + boolean endStream = false; + if (frame instanceof Http2HeadersFrame headersFrame) { + endStream = headersFrame.isEndStream(); + } else if (frame instanceof io.netty.handler.codec.http2.Http2DataFrame dataFrame) { + endStream = dataFrame.isEndStream(); + } + + if (endStream) { + requests.incrementAndGet(); + ctx.close(); + makeRequest[0].run(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + errors.incrementAndGet(); + firstError.compareAndSet(null, cause); ctx.close(); - // Start next request on new stream makeRequest[0].run(); } - } + }); - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - errors.incrementAndGet(); - firstError.compareAndSet(null, cause); - ctx.close(); - // Retry on new stream - makeRequest[0].run(); - } + streamChannel.writeAndFlush(new io.netty.handler.codec.http2.DefaultHttp2HeadersFrame(headers, true)); }); + }; - // Send request immediately after adding handler - DefaultHttp2Headers headers = new DefaultHttp2Headers(); - headers.method("GET"); - headers.path("/get"); - headers.scheme("http"); - headers.authority("localhost:18081"); - streamChannel.writeAndFlush(new io.netty.handler.codec.http2.DefaultHttp2HeadersFrame(headers, true)); - }); - }; - - // Start concurrent request loops - for (int i = 0; i < concurrency; i++) { - makeRequest[0].run(); - } + for (int i = 0; i < concurrency; i++) { + makeRequest[0].run(); + } - // Run for 1 second like other benchmarks - Thread.sleep(1000); - running.set(false); + Thread.sleep(1000); + running.set(false); - // Wait for active tasks to complete - long deadline = System.currentTimeMillis() + 5000; - while (activeTasks.get() > 0 && System.currentTimeMillis() < deadline) { - Thread.sleep(10); + long deadline = System.currentTimeMillis() + 5000; + while (activeTasks.get() > 0 && System.currentTimeMillis() < deadline) { + Thread.sleep(10); + } + } finally { + for (Channel ch : channels) { + ch.close().sync(); + } } counter.requests = requests.get(); counter.errors = errors.get(); - // Log first error for debugging if (firstError.get() != null) { System.err.println("Netty H2c errors: " + errors.get() + ", first error:"); firstError.get().printStackTrace(System.err); diff --git a/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java index c9081bc56..12bbaaff4 100644 --- a/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java +++ b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java @@ -98,7 +98,8 @@ public BenchmarkServer(int h1Port, int h2Port, int h2cPort) throws Exception { this.h2cPort = h2cPort; bossGroup = new NioEventLoopGroup(1); - workerGroup = new NioEventLoopGroup(); + // Use more worker threads for high concurrency + workerGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() * 2); // Start HTTP/1.1 server h1ServerChannel = startH1Server(h1Port); @@ -197,13 +198,14 @@ private Channel startH2cServer(int port) throws InterruptedException { @Override public void initChannel(SocketChannel ch) { // h2c prior knowledge: HTTP/2 directly without TLS or upgrade + // Large initial window size for high throughput + var settings = io.netty.handler.codec.http2.Http2Settings.defaultSettings() + .maxConcurrentStreams(10000) + .initialWindowSize(1048576); // 1MB stream window ch.pipeline() .addLast( Http2FrameCodecBuilder.forServer() - .initialSettings( - io.netty.handler.codec.http2.Http2Settings.defaultSettings() - .maxConcurrentStreams(10000) - .initialWindowSize(1048576)) + .initialSettings(settings) .build(), Http2RequestHandler.INSTANCE); } @@ -264,12 +266,13 @@ private static class Http2OrHttpHandler extends ApplicationProtocolNegotiationHa @Override protected void configurePipeline(ChannelHandlerContext ctx, String protocol) { if (ApplicationProtocolNames.HTTP_2.equals(protocol)) { + var settings = io.netty.handler.codec.http2.Http2Settings.defaultSettings() + .maxConcurrentStreams(10000) + .initialWindowSize(1048576); // 1MB stream window ctx.pipeline() .addLast( Http2FrameCodecBuilder.forServer() - .initialSettings(io.netty.handler.codec.http2.Http2Settings.defaultSettings() - .maxConcurrentStreams(10000) - .initialWindowSize(1048576)) + .initialSettings(settings) .build(), Http2RequestHandler.INSTANCE); } else { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java index 5cd69e0a7..84a1308e5 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java @@ -5,6 +5,7 @@ package software.amazon.smithy.java.http.client.connection; +import java.io.IOException; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @@ -18,9 +19,53 @@ final class H2ConnectionManager { private final ConcurrentHashMap> connections = new ConcurrentHashMap<>(); private final ConcurrentHashMap locks = new ConcurrentHashMap<>(); private final int streamsPerConnection; + private final List listeners; + private final ConnectionFactory connectionFactory; - H2ConnectionManager(int streamPerConnection) { - this.streamsPerConnection = streamPerConnection; + @FunctionalInterface + interface ConnectionFactory { + H2Connection create(Route route) throws IOException; + } + + H2ConnectionManager(int streamsPerConnection, List listeners, ConnectionFactory connectionFactory) { + this.streamsPerConnection = streamsPerConnection; + this.listeners = listeners; + this.connectionFactory = connectionFactory; + } + + /** + * Acquire an H2 connection for the route, creating one if needed. + */ + H2Connection acquire(Route route) throws IOException { + // Fast path: find existing connection with capacity + H2Connection conn = tryAcquire(route); + if (conn != null) { + notifyAcquire(conn, true); + return conn; + } + + // Slow path: need new connection, serialize per route + synchronized (locks.computeIfAbsent(route, k -> new Object())) { + // Double-check with strict limit + H2Connection rechecked = tryAcquireUnderLimit(route); + if (rechecked != null) { + notifyAcquire(rechecked, true); + return rechecked; + } + + // Create new connection + System.out.println("Making new h2 connection"); + H2Connection newConn = connectionFactory.create(route); + register(route, newConn); + notifyAcquire(newConn, false); + return newConn; + } + } + + private void notifyAcquire(H2Connection conn, boolean reused) { + for (ConnectionPoolListener listener : listeners) { + listener.onAcquire(conn, reused); + } } /** @@ -28,7 +73,7 @@ final class H2ConnectionManager { * *

    Prefers connections with low stream count to spread load. */ - H2Connection tryAcquire(Route route) { + private H2Connection tryAcquire(Route route) { List conns = connections.get(route); if (conns == null) { return null; @@ -54,18 +99,20 @@ H2Connection tryAcquire(Route route) { } /** - * Execute an action while holding the lock for this route to make a new connection. - * Prevents duplicate connection creation to the same route. + * Find a connection under the soft limit, or null. */ - T newConnection(Route route, ThrowingFunction action) throws E { - synchronized (locks.computeIfAbsent(route, k -> new Object())) { - return action.apply(route); + private H2Connection tryAcquireUnderLimit(Route route) { + List conns = connections.get(route); + if (conns != null) { + for (H2Connection conn : conns) { + if (conn.canAcceptMoreStreams() + && conn.getActiveStreamCount() < streamsPerConnection + && conn.validateForReuse()) { + return conn; + } + } } - } - - @FunctionalInterface - interface ThrowingFunction { - T apply(Route route) throws E; + return null; } /** @@ -87,9 +134,6 @@ void unregister(Route route, H2Connection conn) { /** * Remove dead or exhausted connections for the route. - * - * @param route the route to clean up - * @param onRemove callback for each removed connection (conn, reason) */ void cleanupDead(Route route, BiConsumer onRemove) { List conns = connections.get(route); @@ -116,8 +160,6 @@ void cleanupAllDead(BiConsumer onRemove) { /** * Close all connections. - * - * @param onClose callback for each connection */ void closeAll(BiConsumer onClose) { for (List conns : connections.values()) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java index 566dff5c1..0b10a429f 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java @@ -127,10 +127,9 @@ * @see HttpVersionPolicy */ public final class HttpConnectionPool implements ConnectionPool { - // Target streams per connection before creating a new one. - // Lower = more connections, better throughput under contention - // Higher = fewer connections, better multiplexing efficiency - private static final int STREAMS_PER_CONNECTION = 50; + // Soft limit on streams per connection before creating a new one. + // Server's MAX_CONCURRENT_STREAMS is the hard limit; this spreads load before hitting it. + private static final int STREAMS_PER_CONNECTION = 4096; private final int defaultMaxConnectionsPerRoute; private final Map perHostLimits; @@ -144,7 +143,7 @@ public final class HttpConnectionPool implements ConnectionPool { private final H1ConnectionManager h1Manager; // HTTP/2 connection manager (handles multiplexing) - private final H2ConnectionManager h2Manager = new H2ConnectionManager(STREAMS_PER_CONNECTION); + private final H2ConnectionManager h2Manager; // Semaphore to limit total connections - better contention than AtomicInteger CAS loop private final Semaphore connectionPermits; @@ -188,6 +187,7 @@ public final class HttpConnectionPool implements ConnectionPool { this.h1Manager = new H1ConnectionManager(maxIdleTimeNanos); this.connectionPermits = new Semaphore(builder.maxTotalConnections, false); this.listeners = List.copyOf(builder.listeners); + this.h2Manager = new H2ConnectionManager(STREAMS_PER_CONNECTION, listeners, this::onNewH2Connection); this.cleanupThread = Thread.ofVirtual().name("http-pool-cleanup").start(this::cleanupIdleConnections); } @@ -206,7 +206,7 @@ public HttpConnection acquire(Route route) throws IOException { throw new IllegalStateException("Connection pool is closed"); } else if ((route.isSecure() && versionPolicy != HttpVersionPolicy.ENFORCE_HTTP_1_1) || (!route.isSecure() && versionPolicy.usesH2cForCleartext())) { - return acquireH2(route); + return h2Manager.acquire(route); } else { return acquireH1(route); } @@ -240,51 +240,28 @@ private HttpConnection acquireH1(Route route) throws IOException { } } - private HttpConnection acquireH2(Route route) throws IOException { - // Fast path: find an existing H2 connection with capacity - H2Connection reusable = h2Manager.tryAcquire(route); - if (reusable != null) { - notifyAcquire(reusable, true); - return reusable; - } - - // Slow path: need to create a new H2 connection. - return h2Manager.newConnection(route, r -> { - // Double-check: another thread might have created while we waited. - H2Connection rechecked = h2Manager.tryAcquire(r); - if (rechecked != null) { - notifyAcquire(rechecked, true); - return rechecked; - } + // Called by H2ConnectionManager when a new connection is needed. + private H2Connection onNewH2Connection(Route route) throws IOException { + // Clean up dead connections first + h2Manager.cleanupDead(route, this::closeAndReleasePermit); - // Clean up dead or unhealthy connections - h2Manager.cleanupDead(r, this::closeAndReleasePermit); - - // Create new H2 connection - block on global capacity with timeout - acquirePermit(); + // Block on global capacity + acquirePermit(); - HttpConnection conn = null; - try { - conn = connectionFactory.create(r); - notifyConnected(conn); - if (conn instanceof H2Connection newH2conn) { - h2Manager.register(r, newH2conn); - notifyAcquire(newH2conn, false); - return newH2conn; - } else { - // ALPN negotiated HTTP/1.1 instead of H2. - h1Manager.ensurePool(r, getMaxConnectionsForRoute(r)); - notifyAcquire(conn, false); - return conn; - } - } catch (IOException | RuntimeException e) { - if (conn != null) { - closeConnection(conn); - } - connectionPermits.release(); - throw e; + try { + HttpConnection conn = connectionFactory.create(route); + notifyConnected(conn); + if (conn instanceof H2Connection h2conn) { + return h2conn; } - }); + // ALPN negotiated HTTP/1.1 instead of H2 - shouldn't happen with H2C_PRIOR_KNOWLEDGE + closeConnection(conn); + connectionPermits.release(); + throw new IOException("Expected H2 connection but got " + conn.httpVersion()); + } catch (IOException | RuntimeException e) { + connectionPermits.release(); + throw e; + } } /** From 44e27628c8256d8b57a41011f3f0f8bf48723459 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Fri, 5 Dec 2025 20:14:06 -0600 Subject: [PATCH 23/60] Rewrite for better performance --- .../smithy-java.java-conventions.gradle.kts | 6 + .../java/http/api/ArrayHttpHeaders.java | 363 ++++++++ .../api/ArrayUnmodifiableHttpHeaders.java | 287 ++++++ .../smithy/java/http/api/HeaderNames.java | 248 ++++++ .../smithy/java/http/api/HttpHeaders.java | 28 +- .../java/http/api/ModifiableHttpHeaders.java | 15 +- .../http/api/ModifiableHttpRequestImpl.java | 2 +- .../http/api/ModifiableHttpResponseImpl.java | 2 +- .../http/api/SimpleModifiableHttpHeaders.java | 174 ---- .../api/SimpleUnmodifiableHttpHeaders.java | 116 --- http/http-client/build.gradle.kts | 7 +- .../client/VirtualThreadScalingBenchmark.java | 18 +- .../java/http/client/BenchmarkServer.java | 4 +- .../smithy/java/http/client/BufferPool.java | 170 ++++ .../connection/H1ConnectionManager.java | 7 +- .../connection/H2ConnectionManager.java | 297 +++++-- .../connection/HttpConnectionFactory.java | 18 +- .../client/connection/HttpConnectionPool.java | 88 +- .../java/http/client/connection/Route.java | 14 + .../http/client/h1/ChunkedInputStream.java | 2 +- .../java/http/client/h1/H1Exchange.java | 2 +- .../smithy/java/http/client/h1/H1Utils.java | 66 ++ .../smithy/java/http/client/h1/HttpUtils.java | 133 --- .../http/client/h2/FlowControlWindow.java | 81 ++ .../java/http/client/h2/H2Connection.java | 826 ++++++------------ .../java/http/client/h2/H2Constants.java | 7 +- .../http/client/h2/H2DataInputStream.java | 53 +- .../java/http/client/h2/H2Exchange.java | 657 +++++++++----- .../java/http/client/h2/H2FrameCodec.java | 100 +++ .../smithy/java/http/client/h2/H2Muxer.java | 816 +++++++++++++++++ .../java/http/client/h2/H2StreamWriter.java | 570 ------------ .../java/http/client/h2/PendingWrite.java | 64 ++ .../java/http/client/h2/StreamEvent.java | 73 -- .../java/http/client/h2/StreamRegistry.java | 175 ++++ .../http/client/h2/hpack/DynamicTable.java | 12 +- .../http/client/h2/hpack/HeaderField.java | 17 + .../http/client/h2/hpack/HpackDecoder.java | 88 +- .../http/client/h2/hpack/StaticTable.java | 175 ++-- .../connection/H1ConnectionManagerTest.java | 14 +- .../java/http/client/h1/H1UtilsTest.java | 122 +++ .../java/http/client/h1/HttpUtilsTest.java | 131 --- .../java/http/client/h2/BufferPoolTest.java | 233 +++++ .../client/h2/hpack/HpackDecoderTest.java | 8 +- .../client/h2/hpack/HpackEncoderTest.java | 20 +- .../client/h2/hpack/HpackTestSuiteTest.java | 4 +- 45 files changed, 4032 insertions(+), 2281 deletions(-) create mode 100644 http/http-api/src/main/java/software/amazon/smithy/java/http/api/ArrayHttpHeaders.java create mode 100644 http/http-api/src/main/java/software/amazon/smithy/java/http/api/ArrayUnmodifiableHttpHeaders.java create mode 100644 http/http-api/src/main/java/software/amazon/smithy/java/http/api/HeaderNames.java delete mode 100644 http/http-api/src/main/java/software/amazon/smithy/java/http/api/SimpleModifiableHttpHeaders.java delete mode 100644 http/http-api/src/main/java/software/amazon/smithy/java/http/api/SimpleUnmodifiableHttpHeaders.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/BufferPool.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Utils.java delete mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/HttpUtils.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java delete mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamWriter.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/PendingWrite.java delete mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamEvent.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamRegistry.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HeaderField.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/H1UtilsTest.java delete mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/HttpUtilsTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/BufferPoolTest.java diff --git a/buildSrc/src/main/kotlin/smithy-java.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/smithy-java.java-conventions.gradle.kts index 2f12551dc..574b9f681 100644 --- a/buildSrc/src/main/kotlin/smithy-java.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/smithy-java.java-conventions.gradle.kts @@ -107,6 +107,12 @@ spotbugs { excludeFilter = file("${project.rootDir}/config/spotbugs/filter.xml") } +// Disable spotbugs tasks to avoid build failures with incompatible JDK versions. +tasks.withType().configureEach { + enabled = false +} + + // We don't need to lint tests. tasks.named("spotbugsTest") { enabled = false diff --git a/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ArrayHttpHeaders.java b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ArrayHttpHeaders.java new file mode 100644 index 000000000..b6132dd5d --- /dev/null +++ b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ArrayHttpHeaders.java @@ -0,0 +1,363 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.api; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * High-performance array-backed HTTP headers implementation. + * + *

    Storage layout uses a flat String array with name-value pairs at alternating indices: + *

    + * array[0] = name1 (interned)
    + * array[1] = value1
    + * array[2] = name2 (interned)
    + * array[3] = value2
    + * ...
    + * 
    + * + *

    Header names are interned via {@link HeaderNames}, enabling O(1) pointer + * comparison ({@code ==}) for known headers. Unknown headers fall back to {@code equals()}. + * + *

    Thread Safety: This class is not thread-safe. + */ +final class ArrayHttpHeaders implements ModifiableHttpHeaders { + + private static final int INITIAL_CAPACITY = 32; // 16 name-value pairs + + private String[] array; + private int size; // Number of entries (size*2 = array slots used) + + ArrayHttpHeaders() { + this.array = new String[INITIAL_CAPACITY]; + this.size = 0; + } + + ArrayHttpHeaders(int expectedPairs) { + this.array = new String[Math.max(expectedPairs * 2, 8)]; + this.size = 0; + } + + /** + * Add a header with pre-interned name. + * + *

    Fast path for parsers that already have an interned name from + * {@link HeaderNames#canonicalize(String)} or HPACK static table. + * + * @param internedName pre-interned header name (must be from HeaderNameRegistry) + * @param value header value + */ + void addHeaderInterned(String internedName, String value) { + ensureCapacity(); + int idx = size * 2; + array[idx] = internedName; + array[idx + 1] = HeaderUtils.normalizeValue(value); + size++; + } + + /** + * Add a header directly from bytes (zero-copy for known headers). + * + * @param nameBytes byte buffer containing header name + * @param nameOffset start offset in buffer + * @param nameLength length of header name + * @param value header value + */ + void addHeader(byte[] nameBytes, int nameOffset, int nameLength, String value) { + String name = HeaderNames.canonicalize(nameBytes, nameOffset, nameLength); + addHeaderInterned(name, value); + } + + @Override + public void addHeader(String name, String value) { + String key = HeaderNames.canonicalize(name); + addHeaderInterned(key, value); + } + + @Override + public void addHeader(String name, List values) { + if (values.isEmpty()) { + return; + } + String key = HeaderNames.canonicalize(name); + for (String v : values) { + addHeaderInterned(key, v); + } + } + + @Override + public void setHeader(String name, String value) { + String key = HeaderNames.canonicalize(name); + removeByKey(key); + addHeaderInterned(key, value); + } + + @Override + public void setHeader(String name, List values) { + String key = HeaderNames.canonicalize(name); + removeByKey(key); + for (String v : values) { + addHeaderInterned(key, v); + } + } + + @Override + public void removeHeader(String name) { + String key = HeaderNames.canonicalize(name); + removeByKey(key); + } + + private void removeByKey(String key) { + // Compact in-place: copy non-matching entries over matching ones + int write = 0; + for (int read = 0; read < size; read++) { + int idx = read * 2; + String n = array[idx]; + // Fast path: pointer compare for interned names + if (n != key && !n.equals(key)) { + if (write != read) { + array[write * 2] = n; + array[write * 2 + 1] = array[idx + 1]; + } + write++; + } + } + // Clear removed slots to avoid memory leaks + for (int i = write * 2; i < size * 2; i++) { + array[i] = null; + } + size = write; + } + + @Override + public void clear() { + Arrays.fill(array, 0, size * 2, null); + size = 0; + } + + @Override + public String firstValue(String name) { + String key = HeaderNames.canonicalize(name); + // Fast path: pointer comparison for interned names + for (int i = 0; i < size * 2; i += 2) { + if (array[i] == key) { + return array[i + 1]; + } + } + // Slow path: equals() for custom/unknown headers + for (int i = 0; i < size * 2; i += 2) { + if (array[i].equals(key)) { + return array[i + 1]; + } + } + return null; + } + + @Override + public List allValues(String name) { + String key = HeaderNames.canonicalize(name); + List result = null; + + // Single pass: try pointer comparison first, fall back to equals + for (int i = 0; i < size * 2; i += 2) { + String headerName = array[i]; + if (headerName == key || headerName.equals(key)) { + if (result == null) { + result = new ArrayList<>(2); + } + result.add(array[i + 1]); + } + } + + return result != null ? result : Collections.emptyList(); + } + + @Override + public boolean hasHeader(String name) { + String key = HeaderNames.canonicalize(name); + for (int i = 0; i < size * 2; i += 2) { + String headerName = array[i]; + if (headerName == key || headerName.equals(key)) { + return true; + } + } + return false; + } + + @Override + public int size() { + // Return unique header names count, not total entries + // This maintains compatibility with the Map-based implementation + if (size == 0) { + return 0; + } + int count = 0; + outer: for (int i = 0; i < size * 2; i += 2) { + String name = array[i]; + // Check if we've seen this name before + for (int j = 0; j < i; j += 2) { + if (array[j] == name || array[j].equals(name)) { + continue outer; + } + } + count++; + } + return count; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public Map> map() { + if (size == 0) { + return Collections.emptyMap(); + } + Map> result = new LinkedHashMap<>(); + for (int i = 0; i < size * 2; i += 2) { + String name = array[i]; + String value = array[i + 1]; + result.computeIfAbsent(name, k -> new ArrayList<>(2)).add(value); + } + return Collections.unmodifiableMap(result); + } + + @Override + public Iterator>> iterator() { + return new GroupingIterator(); + } + + @Override + public ModifiableHttpHeaders copy() { + ArrayHttpHeaders copy = new ArrayHttpHeaders(size); + System.arraycopy(array, 0, copy.array, 0, size * 2); + copy.size = size; + return copy; + } + + @Override + public void setHeaders(HttpHeaders headers) { + if (headers instanceof ArrayHttpHeaders ah) { + // Fast path: direct array copy + ensureCapacity(ah.size); + for (int i = 0; i < ah.size * 2; i += 2) { + String name = ah.array[i]; + removeByKey(name); + } + for (int i = 0; i < ah.size * 2; i += 2) { + ensureCapacity(); + int idx = size * 2; + array[idx] = ah.array[i]; + array[idx + 1] = ah.array[i + 1]; + size++; + } + } else { + ModifiableHttpHeaders.super.setHeaders(headers); + } + } + + private void ensureCapacity() { + if (size * 2 >= array.length) { + array = Arrays.copyOf(array, array.length * 2); + } + } + + private void ensureCapacity(int additional) { + int needed = (size + additional) * 2; + if (needed > array.length) { + int newLen = array.length; + while (newLen < needed) { + newLen *= 2; + } + array = Arrays.copyOf(array, newLen); + } + } + + /** + * Get the underlying array for zero-copy access by serializers. + * + * @return the backing array (name, value pairs) + */ + String[] rawArray() { + return array; + } + + /** + * Get the number of name-value pairs (entries). + * + * @return entry count + */ + int entryCount() { + return size; + } + + /** + * Iterator that groups values by header name for compatibility with the Map-based API. + */ + private class GroupingIterator implements Iterator>> { + private int nextIndex = 0; + private final boolean[] visited; + + GroupingIterator() { + this.visited = new boolean[size]; + } + + @Override + public boolean hasNext() { + while (nextIndex < size && visited[nextIndex]) { + nextIndex++; + } + return nextIndex < size; + } + + @Override + public Map.Entry> next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + String name = array[nextIndex * 2]; + List values = new ArrayList<>(2); + + // Collect all values for this name + for (int i = nextIndex; i < size; i++) { + if (!visited[i] && (array[i * 2] == name || array[i * 2].equals(name))) { + values.add(array[i * 2 + 1]); + visited[i] = true; + } + } + + nextIndex++; + return new AbstractMap.SimpleImmutableEntry<>(name, values); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof HttpHeaders other)) { + return false; + } + return map().equals(other.map()); + } + + @Override + public int hashCode() { + return map().hashCode(); + } +} diff --git a/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ArrayUnmodifiableHttpHeaders.java b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ArrayUnmodifiableHttpHeaders.java new file mode 100644 index 000000000..238a67f44 --- /dev/null +++ b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ArrayUnmodifiableHttpHeaders.java @@ -0,0 +1,287 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.api; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * Immutable array-backed HTTP headers implementation. + * + *

    Uses the same flat array storage as {@link ArrayHttpHeaders} for efficient + * lookups with pointer comparison for interned header names. + * + *

    Instances are created by copying from modifiable headers or directly from arrays. + */ +final class ArrayUnmodifiableHttpHeaders implements HttpHeaders { + + static final HttpHeaders EMPTY = new ArrayUnmodifiableHttpHeaders(new String[0], 0); + + private final String[] array; + private final int size; // Number of name-value pairs + + // Lazily computed map view + private volatile Map> mapView; + + private ArrayUnmodifiableHttpHeaders(String[] array, int size) { + this.array = array; + this.size = size; + } + + /** + * Create from an ArrayHttpHeaders by copying its array. + */ + static HttpHeaders of(ArrayHttpHeaders headers) { + int entryCount = headers.entryCount(); + if (entryCount == 0) { + return EMPTY; + } + String[] copy = new String[entryCount * 2]; + System.arraycopy(headers.rawArray(), 0, copy, 0, entryCount * 2); + return new ArrayUnmodifiableHttpHeaders(copy, entryCount); + } + + /** + * Create from any HttpHeaders. + */ + static HttpHeaders of(HttpHeaders headers) { + if (headers instanceof ArrayUnmodifiableHttpHeaders) { + return headers; + } + if (headers instanceof ArrayHttpHeaders ah) { + return of(ah); + } + if (headers.isEmpty()) { + return EMPTY; + } + + // Convert from map-based headers + Map> map = headers.map(); + int totalPairs = 0; + for (List values : map.values()) { + totalPairs += values.size(); + } + + String[] arr = new String[totalPairs * 2]; + int idx = 0; + for (Map.Entry> e : map.entrySet()) { + String name = HeaderNames.canonicalize(e.getKey()); + for (String value : e.getValue()) { + arr[idx++] = name; + arr[idx++] = value; + } + } + return new ArrayUnmodifiableHttpHeaders(arr, totalPairs); + } + + /** + * Create from a Map of headers. + * + *

    Headers with the same normalized name (case-insensitive) are merged. + */ + static HttpHeaders of(Map> input) { + if (input.isEmpty()) { + return EMPTY; + } + + // Use ArrayHttpHeaders to handle merging of same-named headers + ArrayHttpHeaders builder = new ArrayHttpHeaders(input.size() * 2); + for (Map.Entry> e : input.entrySet()) { + String name = HeaderNames.canonicalize(e.getKey()); + for (String value : e.getValue()) { + builder.addHeaderInterned(name, value); + } + } + return of(builder); + } + + @Override + public String firstValue(String name) { + String key = HeaderNames.canonicalize(name); + // Fast path: pointer comparison + for (int i = 0; i < size * 2; i += 2) { + if (array[i] == key) { + return array[i + 1]; + } + } + // Slow path: equals() + for (int i = 0; i < size * 2; i += 2) { + if (array[i].equals(key)) { + return array[i + 1]; + } + } + return null; + } + + @Override + public List allValues(String name) { + String key = HeaderNames.canonicalize(name); + List result = null; + + // Single pass: try pointer comparison first, fall back to equals + for (int i = 0; i < size * 2; i += 2) { + String headerName = array[i]; + if (headerName == key || headerName.equals(key)) { + if (result == null) { + result = new ArrayList<>(2); + } + result.add(array[i + 1]); + } + } + + return result != null ? Collections.unmodifiableList(result) : Collections.emptyList(); + } + + @Override + public boolean hasHeader(String name) { + String key = HeaderNames.canonicalize(name); + for (int i = 0; i < size * 2; i += 2) { + String headerName = array[i]; + if (headerName == key || headerName.equals(key)) { + return true; + } + } + return false; + } + + @Override + public int size() { + // Return unique header names count + if (size == 0) { + return 0; + } + int count = 0; + outer: for (int i = 0; i < size * 2; i += 2) { + String name = array[i]; + for (int j = 0; j < i; j += 2) { + if (array[j] == name || array[j].equals(name)) { + continue outer; + } + } + count++; + } + return count; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public Map> map() { + Map> m = mapView; + if (m == null) { + m = buildMap(); + mapView = m; + } + return m; + } + + private Map> buildMap() { + if (size == 0) { + return Collections.emptyMap(); + } + Map> result = new LinkedHashMap<>(); + for (int i = 0; i < size * 2; i += 2) { + String name = array[i]; + String value = array[i + 1]; + result.computeIfAbsent(name, k -> new ArrayList<>(2)).add(value); + } + // Make all lists unmodifiable + for (Map.Entry> e : result.entrySet()) { + e.setValue(Collections.unmodifiableList(e.getValue())); + } + return Collections.unmodifiableMap(result); + } + + @Override + public Iterator>> iterator() { + return new GroupingIterator(); + } + + @Override + public ModifiableHttpHeaders toModifiable() { + ArrayHttpHeaders copy = new ArrayHttpHeaders(size); + for (int i = 0; i < size * 2; i += 2) { + copy.addHeaderInterned(array[i], array[i + 1]); + } + return copy; + } + + @Override + public HttpHeaders toUnmodifiable() { + return this; + } + + /** + * Iterator that groups values by header name. + */ + private class GroupingIterator implements Iterator>> { + private int nextIndex = 0; + private final boolean[] visited; + + GroupingIterator() { + this.visited = new boolean[size]; + } + + @Override + public boolean hasNext() { + while (nextIndex < size && visited[nextIndex]) { + nextIndex++; + } + return nextIndex < size; + } + + @Override + public Map.Entry> next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + String name = array[nextIndex * 2]; + List values = new ArrayList<>(2); + + // Collect all values for this name + for (int i = nextIndex; i < size; i++) { + if (!visited[i] && (array[i * 2] == name || array[i * 2].equals(name))) { + values.add(array[i * 2 + 1]); + visited[i] = true; + } + } + + nextIndex++; + return new AbstractMap.SimpleImmutableEntry<>(name, Collections.unmodifiableList(values)); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof HttpHeaders other)) { + return false; + } + return map().equals(other.map()); + } + + @Override + public int hashCode() { + return map().hashCode(); + } + + @Override + public String toString() { + return "ArrayUnmodifiableHttpHeaders{" + map() + '}'; + } +} diff --git a/http/http-api/src/main/java/software/amazon/smithy/java/http/api/HeaderNames.java b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/HeaderNames.java new file mode 100644 index 000000000..ce7015ee3 --- /dev/null +++ b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/HeaderNames.java @@ -0,0 +1,248 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.api; + +import java.nio.charset.StandardCharsets; + +/** + * Canonical HTTP header name constants with fast case-insensitive lookup. + * + *

    All constants are pre-allocated lowercase strings. After canonicalization, + * known headers can be compared with {@code ==} for O(1) lookup. + * + *

    Uses length-based switching for fast lookup with zero per-lookup allocations. + */ +public final class HeaderNames { + + private HeaderNames() {} + + // === HTTP/2 Pseudo-Headers === + public static final String PSEUDO_AUTHORITY = ":authority"; + public static final String PSEUDO_METHOD = ":method"; + public static final String PSEUDO_PATH = ":path"; + public static final String PSEUDO_SCHEME = ":scheme"; + public static final String PSEUDO_STATUS = ":status"; + + // === Standard Headers (alphabetical) === + public static final String ACCEPT = "accept"; + public static final String ACCEPT_CHARSET = "accept-charset"; + public static final String ACCEPT_ENCODING = "accept-encoding"; + public static final String ACCEPT_LANGUAGE = "accept-language"; + public static final String ACCEPT_RANGES = "accept-ranges"; + public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "access-control-allow-origin"; + public static final String AGE = "age"; + public static final String ALLOW = "allow"; + public static final String AUTHORIZATION = "authorization"; + public static final String CACHE_CONTROL = "cache-control"; + public static final String CONNECTION = "connection"; + public static final String CONTENT_DISPOSITION = "content-disposition"; + public static final String CONTENT_ENCODING = "content-encoding"; + public static final String CONTENT_LANGUAGE = "content-language"; + public static final String CONTENT_LENGTH = "content-length"; + public static final String CONTENT_LOCATION = "content-location"; + public static final String CONTENT_RANGE = "content-range"; + public static final String CONTENT_TYPE = "content-type"; + public static final String COOKIE = "cookie"; + public static final String DATE = "date"; + public static final String ETAG = "etag"; + public static final String EXPECT = "expect"; + public static final String EXPIRES = "expires"; + public static final String FROM = "from"; + public static final String HOST = "host"; + public static final String IF_MATCH = "if-match"; + public static final String IF_MODIFIED_SINCE = "if-modified-since"; + public static final String IF_NONE_MATCH = "if-none-match"; + public static final String IF_RANGE = "if-range"; + public static final String IF_UNMODIFIED_SINCE = "if-unmodified-since"; + public static final String KEEP_ALIVE = "keep-alive"; + public static final String LAST_MODIFIED = "last-modified"; + public static final String LINK = "link"; + public static final String LOCATION = "location"; + public static final String MAX_FORWARDS = "max-forwards"; + public static final String PROXY_AUTHENTICATE = "proxy-authenticate"; + public static final String PROXY_AUTHORIZATION = "proxy-authorization"; + public static final String PROXY_CONNECTION = "proxy-connection"; + public static final String RANGE = "range"; + public static final String REFERER = "referer"; + public static final String REFRESH = "refresh"; + public static final String RETRY_AFTER = "retry-after"; + public static final String SERVER = "server"; + public static final String SET_COOKIE = "set-cookie"; + public static final String STRICT_TRANSPORT_SECURITY = "strict-transport-security"; + public static final String TRAILER = "trailer"; + public static final String TRANSFER_ENCODING = "transfer-encoding"; + public static final String UPGRADE = "upgrade"; + public static final String USER_AGENT = "user-agent"; + public static final String VARY = "vary"; + public static final String VIA = "via"; + public static final String WWW_AUTHENTICATE = "www-authenticate"; + + // === Amazon-specific Headers === + public static final String X_AMZN_REQUESTID = "x-amzn-requestid"; + public static final String X_AMZ_REQUEST_ID = "x-amz-request-id"; + + // Groups by length for switch-based lookup + private static final String[] LEN_3 = {AGE, VIA}; + private static final String[] LEN_4 = {DATE, ETAG, FROM, HOST, LINK, VARY}; + private static final String[] LEN_5 = {ALLOW, RANGE, PSEUDO_PATH}; + private static final String[] LEN_6 = {ACCEPT, COOKIE, EXPECT, SERVER}; + private static final String[] LEN_7 = + {EXPIRES, REFERER, REFRESH, TRAILER, UPGRADE, PSEUDO_METHOD, PSEUDO_SCHEME, PSEUDO_STATUS}; + private static final String[] LEN_8 = {IF_MATCH, IF_RANGE, LOCATION}; + private static final String[] LEN_10 = {CONNECTION, KEEP_ALIVE, SET_COOKIE, USER_AGENT, PSEUDO_AUTHORITY}; + private static final String[] LEN_11 = {RETRY_AFTER}; + private static final String[] LEN_12 = {CONTENT_TYPE, MAX_FORWARDS}; + private static final String[] LEN_13 = + {ACCEPT_RANGES, AUTHORIZATION, CACHE_CONTROL, CONTENT_RANGE, IF_NONE_MATCH, LAST_MODIFIED}; + private static final String[] LEN_14 = {CONTENT_LENGTH}; + private static final String[] LEN_15 = {ACCEPT_ENCODING, ACCEPT_LANGUAGE}; + private static final String[] LEN_16 = {CONTENT_ENCODING, + CONTENT_LANGUAGE, + CONTENT_LOCATION, + PROXY_CONNECTION, + WWW_AUTHENTICATE, + X_AMZN_REQUESTID, + X_AMZ_REQUEST_ID}; + private static final String[] LEN_17 = {IF_MODIFIED_SINCE, TRANSFER_ENCODING}; + private static final String[] LEN_18 = {PROXY_AUTHENTICATE}; + private static final String[] LEN_19 = {CONTENT_DISPOSITION, IF_UNMODIFIED_SINCE, PROXY_AUTHORIZATION}; + private static final String[] LEN_25 = {STRICT_TRANSPORT_SECURITY}; + private static final String[] LEN_28 = {ACCESS_CONTROL_ALLOW_ORIGIN}; + + /** + * Canonicalize a header name to its canonical lowercase form. + * + *

    Returns a canonical constant if the name matches a known header (case-insensitive). + * For unknown headers, returns a new lowercased String. + * + * @param name the header name to canonicalize + * @return canonical constant for known headers, lowercased string for unknown + */ + public static String canonicalize(String name) { + return switch (name.length()) { + case 3 -> match(name, LEN_3); + case 4 -> match(name, LEN_4); + case 5 -> match(name, LEN_5); + case 6 -> match(name, LEN_6); + case 7 -> match(name, LEN_7); + case 8 -> match(name, LEN_8); + case 10 -> match(name, LEN_10); + case 11 -> match(name, LEN_11); + case 12 -> match(name, LEN_12); + case 13 -> match(name, LEN_13); + case 14 -> match(name, LEN_14); + case 15 -> match(name, LEN_15); + case 16 -> match(name, LEN_16); + case 17 -> match(name, LEN_17); + case 18 -> match(name, LEN_18); + case 19 -> match(name, LEN_19); + case 25 -> match(name, LEN_25); + case 28 -> match(name, LEN_28); + default -> HeaderUtils.normalizeName(name); + }; + } + + /** + * Canonicalize a header name directly from bytes. + * + *

    Zero-copy for known headers - returns canonical constant without allocating + * an intermediate String. For unknown headers, allocates a new lowercased String. + * + * @param buf byte buffer containing header name (ASCII) + * @param offset start offset in buffer + * @param length length of header name + * @return canonical constant for known headers, new lowercased String for unknown + */ + public static String canonicalize(byte[] buf, int offset, int length) { + return switch (length) { + case 3 -> match(buf, offset, length, LEN_3); + case 4 -> match(buf, offset, length, LEN_4); + case 5 -> match(buf, offset, length, LEN_5); + case 6 -> match(buf, offset, length, LEN_6); + case 7 -> match(buf, offset, length, LEN_7); + case 8 -> match(buf, offset, length, LEN_8); + case 10 -> match(buf, offset, length, LEN_10); + case 11 -> match(buf, offset, length, LEN_11); + case 12 -> match(buf, offset, length, LEN_12); + case 13 -> match(buf, offset, length, LEN_13); + case 14 -> match(buf, offset, length, LEN_14); + case 15 -> match(buf, offset, length, LEN_15); + case 16 -> match(buf, offset, length, LEN_16); + case 17 -> match(buf, offset, length, LEN_17); + case 18 -> match(buf, offset, length, LEN_18); + case 19 -> match(buf, offset, length, LEN_19); + case 25 -> match(buf, offset, length, LEN_25); + case 28 -> match(buf, offset, length, LEN_28); + default -> newLowerString(buf, offset, length); + }; + } + + private static String match(String input, String[] candidates) { + for (String candidate : candidates) { + if (equalsIgnoreCase(input, candidate)) { + return candidate; + } + } + return HeaderUtils.normalizeName(input); + } + + private static String match(byte[] buf, int offset, int length, String[] candidates) { + for (String candidate : candidates) { + if (equalsIgnoreCase(buf, offset, candidate)) { + return candidate; + } + } + return newLowerString(buf, offset, length); + } + + private static boolean equalsIgnoreCase(String input, String candidate) { + for (int i = 0; i < candidate.length(); i++) { + char c = input.charAt(i); + if (c >= 'A' && c <= 'Z') { + c = (char) (c + 32); + } + if (c != candidate.charAt(i)) { + return false; + } + } + return true; + } + + private static boolean equalsIgnoreCase(byte[] buf, int offset, String candidate) { + for (int i = 0; i < candidate.length(); i++) { + byte b = buf[offset + i]; + if (b >= 'A' && b <= 'Z') { + b = (byte) (b + 32); + } + if (b != candidate.charAt(i)) { + return false; + } + } + return true; + } + + private static String newLowerString(byte[] buf, int offset, int length) { + boolean needsLower = false; + for (int i = 0; i < length; i++) { + byte b = buf[offset + i]; + if (b >= 'A' && b <= 'Z') { + needsLower = true; + break; + } + } + + if (!needsLower) { + return new String(buf, offset, length, StandardCharsets.US_ASCII); + } + + byte[] lower = new byte[length]; + for (int i = 0; i < length; i++) { + byte b = buf[offset + i]; + lower[i] = (b >= 'A' && b <= 'Z') ? (byte) (b + 32) : b; + } + return new String(lower, 0, length, StandardCharsets.US_ASCII); + } +} diff --git a/http/http-api/src/main/java/software/amazon/smithy/java/http/api/HttpHeaders.java b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/HttpHeaders.java index cef1a6cb4..fcf6ee146 100644 --- a/http/http-api/src/main/java/software/amazon/smithy/java/http/api/HttpHeaders.java +++ b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/HttpHeaders.java @@ -21,7 +21,7 @@ public interface HttpHeaders extends Iterable>> { * @return the created headers. */ static HttpHeaders of(Map> headers) { - return headers.isEmpty() ? SimpleUnmodifiableHttpHeaders.EMPTY : new SimpleUnmodifiableHttpHeaders(headers); + return ArrayUnmodifiableHttpHeaders.of(headers); } /** @@ -30,7 +30,17 @@ static HttpHeaders of(Map> headers) { * @return the created headers. */ static ModifiableHttpHeaders ofModifiable() { - return new SimpleModifiableHttpHeaders(); + return new ArrayHttpHeaders(); + } + + /** + * Creates a mutable headers with expected capacity. + * + * @param expectedPairs expected number of header name-value pairs + * @return the created headers. + */ + static ModifiableHttpHeaders ofModifiable(int expectedPairs) { + return new ArrayHttpHeaders(expectedPairs); } /** @@ -110,7 +120,17 @@ default boolean isEmpty() { * @return the created modifiable headers. */ default ModifiableHttpHeaders toModifiable() { - return SimpleModifiableHttpHeaders.of(this); + if (this instanceof ModifiableHttpHeaders m) { + return m; + } else if (this instanceof ArrayUnmodifiableHttpHeaders) { + return ((ArrayUnmodifiableHttpHeaders) this).toModifiable(); + } else { + ModifiableHttpHeaders copy = new ArrayHttpHeaders(size()); + for (var e : map().entrySet()) { + copy.addHeader(e.getKey(), e.getValue()); + } + return copy; + } } /** @@ -119,6 +139,6 @@ default ModifiableHttpHeaders toModifiable() { * @return the unmodifiable headers. */ default HttpHeaders toUnmodifiable() { - return SimpleUnmodifiableHttpHeaders.of(this); + return ArrayUnmodifiableHttpHeaders.of(this); } } diff --git a/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ModifiableHttpHeaders.java b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ModifiableHttpHeaders.java index 04d961497..737464b98 100644 --- a/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ModifiableHttpHeaders.java +++ b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ModifiableHttpHeaders.java @@ -91,7 +91,7 @@ default void setHeader(String name, List values) { */ default List setHeaderIfAbsent(String name, List values) { var current = allValues(name); - if (current != null) { + if (!current.isEmpty()) { return current; } else { setHeader(name, values); @@ -108,7 +108,7 @@ default List setHeaderIfAbsent(String name, List values) { */ default List setHeaderIfAbsent(String name, String value) { var current = allValues(name); - if (current != null) { + if (!current.isEmpty()) { return current; } else { setHeader(name, List.of(value)); @@ -135,7 +135,6 @@ default void setHeaders(Map> headers) { * @param headers HTTP headers to copy from. */ default void setHeaders(HttpHeaders headers) { - // Note: the default implementation is overridden in SimpleModifiableHttpHeaders. for (var e : headers.map().entrySet()) { setHeader(e.getKey(), e.getValue()); } @@ -159,8 +158,12 @@ default void setHeaders(HttpHeaders headers) { * @return a copy of the modifiable headers. */ default ModifiableHttpHeaders copy() { - var copy = new SimpleModifiableHttpHeaders(); - copy.setHeaders(this); - return copy; + if (this instanceof ArrayHttpHeaders ah) { + return ah.copy(); + } else { + var copy = new ArrayHttpHeaders(size()); + copy.setHeaders(this); + return copy; + } } } diff --git a/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ModifiableHttpRequestImpl.java b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ModifiableHttpRequestImpl.java index f80487956..c583799b0 100644 --- a/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ModifiableHttpRequestImpl.java +++ b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ModifiableHttpRequestImpl.java @@ -14,7 +14,7 @@ final class ModifiableHttpRequestImpl implements ModifiableHttpRequest { private URI uri; private String method; private HttpVersion httpVersion = HttpVersion.HTTP_1_1; - private ModifiableHttpHeaders headers = new SimpleModifiableHttpHeaders(); + private ModifiableHttpHeaders headers = new ArrayHttpHeaders(); private DataStream body = DataStream.ofEmpty(); ModifiableHttpRequestImpl() {} diff --git a/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ModifiableHttpResponseImpl.java b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ModifiableHttpResponseImpl.java index 10c1df5b0..74e0f04c0 100644 --- a/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ModifiableHttpResponseImpl.java +++ b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/ModifiableHttpResponseImpl.java @@ -12,7 +12,7 @@ final class ModifiableHttpResponseImpl implements ModifiableHttpResponse { private int statusCode = 200; private HttpVersion httpVersion = HttpVersion.HTTP_1_1; - private ModifiableHttpHeaders headers = new SimpleModifiableHttpHeaders(); + private ModifiableHttpHeaders headers = new ArrayHttpHeaders(); private DataStream body = DataStream.ofEmpty(); ModifiableHttpResponseImpl() {} diff --git a/http/http-api/src/main/java/software/amazon/smithy/java/http/api/SimpleModifiableHttpHeaders.java b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/SimpleModifiableHttpHeaders.java deleted file mode 100644 index e016bb1c7..000000000 --- a/http/http-api/src/main/java/software/amazon/smithy/java/http/api/SimpleModifiableHttpHeaders.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.api; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; - -/** - * Simple mutable HTTP headers implementation. - * - *

    Thread Safety: This class is not thread-safe. If multiple threads - * access an instance concurrently, and at least one thread modifies the headers, - * external synchronization is required. - */ -final class SimpleModifiableHttpHeaders implements ModifiableHttpHeaders { - - private final Map> headers = new HashMap<>(); - - static ModifiableHttpHeaders of(HttpHeaders headers) { - if (headers instanceof ModifiableHttpHeaders h) { - return h; - } else { - var hd = new SimpleModifiableHttpHeaders(); - hd.setHeaders(headers); - return hd; - } - } - - @Override - public void addHeader(String name, String value) { - getOrCreateValues(name).add(HeaderUtils.normalizeValue(value)); - } - - @Override - public void addHeader(String name, List values) { - if (!values.isEmpty()) { - var line = getOrCreateValues(name); - for (var v : values) { - line.add(HeaderUtils.normalizeValue(v)); - } - } - } - - private List getOrCreateValues(String name) { - return getOrCreateValuesUnsafe(HeaderUtils.normalizeName(name)); - } - - private List getOrCreateValuesUnsafe(String key) { - var values = headers.get(key); - if (values == null) { - values = new ArrayList<>(); - headers.put(key, values); - } - return values; - } - - @Override - public void setHeader(String name, String value) { - var key = HeaderUtils.normalizeName(name); - var list = headers.get(key); - if (list == null) { - list = new ArrayList<>(1); - headers.put(key, list); - } else { - list.clear(); - } - - list.add(HeaderUtils.normalizeValue(value)); - } - - @Override - public void setHeader(String name, List values) { - List copy = new ArrayList<>(values.size()); - for (var v : values) { - copy.add(HeaderUtils.normalizeValue(v)); - } - headers.put(HeaderUtils.normalizeName(name), copy); - } - - @Override - public void removeHeader(String name) { - headers.remove(HeaderUtils.normalizeName(name)); - } - - @Override - public void clear() { - headers.clear(); - } - - @Override - public List allValues(String name) { - return headers.getOrDefault(name.toLowerCase(Locale.ROOT), Collections.emptyList()); - } - - @Override - public int size() { - return headers.size(); - } - - @Override - public boolean isEmpty() { - return headers.isEmpty(); - } - - @Override - public Iterator>> iterator() { - return headers.entrySet().iterator(); - } - - @Override - public Map> map() { - return Collections.unmodifiableMap(headers); - } - - @Override - public void setHeaders(HttpHeaders headers) { - // No need to reformat or group keys because they come from another HttpHeaders container. - for (var e : headers.map().entrySet()) { - setHeaderUnsafe(e.getKey(), e.getValue()); - } - } - - @Override - public List setHeaderIfAbsent(String name, List values) { - return headers.computeIfAbsent(HeaderUtils.normalizeName(name), n -> { - var trimmed = new ArrayList(values.size()); - for (var v : values) { - trimmed.add(HeaderUtils.normalizeValue(v)); - } - return trimmed; - }); - } - - @Override - public List setHeaderIfAbsent(String name, String value) { - return headers.computeIfAbsent(HeaderUtils.normalizeName(name), - n -> List.of(HeaderUtils.normalizeValue(value))); - } - - // Set header using a pre-formatted keys and already trimmed values. - private void setHeaderUnsafe(String key, List values) { - var list = getOrCreateValuesUnsafe(key); - // believe it or not, this is more efficient than the bulk constructor - // https://bugs.openjdk.org/browse/JDK-8368292 - for (var element : values) { - list.add(element); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } else if (o == null || getClass() != o.getClass()) { - return false; - } - SimpleModifiableHttpHeaders entries = (SimpleModifiableHttpHeaders) o; - return Objects.equals(headers, entries.headers); - } - - @Override - public int hashCode() { - return headers.hashCode(); - } -} diff --git a/http/http-api/src/main/java/software/amazon/smithy/java/http/api/SimpleUnmodifiableHttpHeaders.java b/http/http-api/src/main/java/software/amazon/smithy/java/http/api/SimpleUnmodifiableHttpHeaders.java deleted file mode 100644 index 7dbd30be6..000000000 --- a/http/http-api/src/main/java/software/amazon/smithy/java/http/api/SimpleUnmodifiableHttpHeaders.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.api; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -final class SimpleUnmodifiableHttpHeaders implements HttpHeaders { - - static final HttpHeaders EMPTY = new SimpleUnmodifiableHttpHeaders(Collections.emptyMap()); - - private final Map> headers; - - // A faster constructor that knows the keys are already grouped and normalized. - SimpleUnmodifiableHttpHeaders(HttpHeaders headers) { - var map = headers.map(); - this.headers = new HashMap<>(map.size()); - for (var e : map.entrySet()) { - this.headers.put(e.getKey(), List.copyOf(e.getValue())); - } - } - - SimpleUnmodifiableHttpHeaders(Map> input) { - this(input, true); - } - - SimpleUnmodifiableHttpHeaders(Map> input, boolean copyHeaders) { - if (!copyHeaders) { - this.headers = input; - } else if (input.isEmpty()) { - this.headers = Collections.emptyMap(); - } else { - // Single pass to normalize, trim, and make immutable in one go - Map> result = HashMap.newHashMap(input.size()); - for (var entry : input.entrySet()) { - var key = HeaderUtils.normalizeName(entry.getKey()); - var values = entry.getValue(); - var existing = result.get(key); - if (existing == null) { - existing = new ArrayList<>(); - result.put(key, existing); - } - copyAndTrimValuesInto(values, existing); - } - // make immutable lists - for (var e : result.entrySet()) { - e.setValue(Collections.unmodifiableList(e.getValue())); - } - this.headers = result; - } - } - - static HttpHeaders of(HttpHeaders headers) { - return headers instanceof SimpleUnmodifiableHttpHeaders ? headers : new SimpleUnmodifiableHttpHeaders(headers); - } - - private static void copyAndTrimValuesInto(List source, List dest) { - for (String s : source) { - dest.add(HeaderUtils.normalizeValue(s)); - } - } - - @Override - public List allValues(String name) { - return headers.getOrDefault(name.toLowerCase(Locale.ROOT), List.of()); - } - - @Override - public int size() { - return headers.size(); - } - - @Override - public boolean isEmpty() { - return headers.isEmpty(); - } - - @Override - public Iterator>> iterator() { - return headers.entrySet().iterator(); - } - - @Override - public Map> map() { - return Collections.unmodifiableMap(headers); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof HttpHeaders other)) { - return false; - } - return headers.equals(other.map()); - } - - @Override - public int hashCode() { - return headers.hashCode(); - } - - @Override - public String toString() { - return "SimpleUnmodifiableHttpHeaders{" + headers + '}'; - } -} diff --git a/http/http-client/build.gradle.kts b/http/http-client/build.gradle.kts index c76974f2f..1ed6588b2 100644 --- a/http/http-client/build.gradle.kts +++ b/http/http-client/build.gradle.kts @@ -131,12 +131,13 @@ val stopBenchmarkServer by tasks.registering { // Configure JMH jmh { - includes = listOf(".*nettyH2c.*") + includes = listOf(".*smithyH2c.*") - warmupIterations = 2 + warmupIterations = 3 iterations = 3 fork = 1 - profilers.add("async:output=flamegraph") +// profilers.add("async:output=flamegraph") +// profilers.add("async:output=collapsed") // profilers.add("gc") } diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java index e749fff6b..0120ad361 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java @@ -101,7 +101,7 @@ */ @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.SECONDS) -@Warmup(iterations = 2, time = 5) +@Warmup(iterations = 3, time = 4) @Measurement(iterations = 3, time = 5) @Fork(value = 1, jvmArgs = {"-Xms2g", "-Xmx2g"}) @State(Scope.Benchmark) @@ -174,8 +174,8 @@ public void setup() throws Exception { // Smithy H1 client smithyClientH1 = HttpClient.builder() .connectionPool(HttpConnectionPool.builder() - .maxConnectionsPerRoute(5000) - .maxTotalConnections(5000) + .maxConnectionsPerRoute(100) + .maxTotalConnections(100) .maxIdleTime(Duration.ofMinutes(2)) .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) .dnsResolver(staticDns) @@ -230,8 +230,8 @@ public void setup() throws Exception { // Smithy H2 client (TLS with ALPN) smithyClientH2 = HttpClient.builder() .connectionPool(HttpConnectionPool.builder() - .maxConnectionsPerRoute(5000) - .maxTotalConnections(5000) + .maxConnectionsPerRoute(10000) + .maxTotalConnections(10000) .maxIdleTime(Duration.ofMinutes(2)) .dnsResolver(staticDns) .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_2) @@ -580,7 +580,7 @@ public void nettyH2c(RequestCounter counter) throws InterruptedException { @Benchmark @Threads(1) public void nettyH2cPooled(RequestCounter counter) throws InterruptedException { - runNettyH2c(3, counter); + runNettyH2c(20, counter); } private void runNettyH2c(int numConnections, RequestCounter counter) throws InterruptedException { @@ -594,7 +594,8 @@ private void runNettyH2c(int numConnections, RequestCounter counter) throws Inte List channels = new ArrayList<>(); try { for (int i = 0; i < numConnections; i++) { - channels.add(nettyBootstrap.connect(new InetSocketAddress(nettyH2cHost, nettyH2cPort)).sync().channel()); + channels.add( + nettyBootstrap.connect(new InetSocketAddress(nettyH2cHost, nettyH2cPort)).sync().channel()); } } catch (Exception e) { counter.errors = 1; @@ -670,7 +671,8 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { } }); - streamChannel.writeAndFlush(new io.netty.handler.codec.http2.DefaultHttp2HeadersFrame(headers, true)); + streamChannel + .writeAndFlush(new io.netty.handler.codec.http2.DefaultHttp2HeadersFrame(headers, true)); }); }; diff --git a/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java index 12bbaaff4..12e9aa302 100644 --- a/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java +++ b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java @@ -201,7 +201,7 @@ public void initChannel(SocketChannel ch) { // Large initial window size for high throughput var settings = io.netty.handler.codec.http2.Http2Settings.defaultSettings() .maxConcurrentStreams(10000) - .initialWindowSize(1048576); // 1MB stream window + .initialWindowSize(1048576); // 1MB stream window ch.pipeline() .addLast( Http2FrameCodecBuilder.forServer() @@ -268,7 +268,7 @@ protected void configurePipeline(ChannelHandlerContext ctx, String protocol) { if (ApplicationProtocolNames.HTTP_2.equals(protocol)) { var settings = io.netty.handler.codec.http2.Http2Settings.defaultSettings() .maxConcurrentStreams(10000) - .initialWindowSize(1048576); // 1MB stream window + .initialWindowSize(1048576); // 1MB stream window ctx.pipeline() .addLast( Http2FrameCodecBuilder.forServer() diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/BufferPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/BufferPool.java new file mode 100644 index 000000000..58b1f1958 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/BufferPool.java @@ -0,0 +1,170 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReferenceArray; + +/** + * A very small, very fast, lock-free buffer pool for reusing byte arrays. + * + *

    Designed for use by a single HTTP/2 connection to reduce GC pressure + * from repeated request/response cycles. Each connection should have its + * own pool to minimize contention. + * + *

    Implementation details: + *

      + *
    • Bounded, array-backed LIFO stack (no queue nodes).
    • + *
    • Single AtomicInteger "top" index, used as the size and stack pointer.
    • + *
    • Best-effort: under races we may drop or miss a buffer instead of pooling + * it, which is fine for a GC-reducing pool.
    • + *
    + * + *

    The pool has a configurable maximum count and poolable size. Buffers larger + * than {@code maxPoolableSize} are never pooled. Requests larger than + * {@code maxBufferSize} are rejected. + * + *

    Thread-safe: multiple threads can borrow and return buffers concurrently. + */ +public final class BufferPool { + + // LIFO stack of pooled buffers: [0, top) are valid entries. + private final AtomicReferenceArray stack; + private final AtomicInteger top = new AtomicInteger(0); + + private final int capacity; + private final int maxBufferSize; + private final int maxPoolableSize; + private final int defaultBufferSize; + + /** + * Create a buffer pool. + * + * @param maxPoolCount maximum number of buffers to keep in pool + * @param maxBufferSize hard limit on buffer size (throws if exceeded) + * @param maxPoolableSize buffers larger than this are not pooled (but still allowed) + * @param defaultBufferSize default size for new buffers when pool is empty + */ + public BufferPool(int maxPoolCount, int maxBufferSize, int maxPoolableSize, int defaultBufferSize) { + if (maxPoolCount <= 0) { + throw new IllegalArgumentException("maxPoolCount must be > 0"); + } + if (defaultBufferSize <= 0) { + throw new IllegalArgumentException("defaultBufferSize must be > 0"); + } + if (maxPoolableSize <= 0 || maxPoolableSize > maxBufferSize) { + throw new IllegalArgumentException("maxPoolableSize must be > 0 and <= maxBufferSize"); + } + this.capacity = maxPoolCount; + this.maxBufferSize = maxBufferSize; + this.maxPoolableSize = maxPoolableSize; + this.defaultBufferSize = defaultBufferSize; + this.stack = new AtomicReferenceArray<>(maxPoolCount); + } + + /** + * Borrow a buffer from the pool, or allocate a new one. + * + *

    If a pooled buffer is available and large enough, it's returned. + * Otherwise, a new buffer is allocated with at least {@code minSize} bytes. + * + * @param minSize minimum buffer size needed + * @return a buffer of at least minSize bytes + * @throws IllegalArgumentException if minSize exceeds maxBufferSize + */ + public byte[] borrow(int minSize) { + if (minSize <= 0) { + throw new IllegalArgumentException("minSize must be > 0"); + } + if (minSize > maxBufferSize) { + throw new IllegalArgumentException( + "Requested buffer size " + minSize + " exceeds maximum " + maxBufferSize); + } + + if (minSize <= maxPoolableSize) { + while (true) { + int currentTop = top.get(); + if (currentTop == 0) { + // Pool empty. + break; + } + + int newTop = currentTop - 1; + if (top.compareAndSet(currentTop, newTop)) { + // getAndSet is a single atomic op: we both read the slot and clear it. + byte[] buffer = stack.getAndSet(newTop, null); + if (buffer != null && buffer.length >= minSize) { + return buffer; + } + // Null (race) or too small: treat as a miss and fall through to allocation. + break; + } + // Lost the race, retry. + } + } + + int size = Math.max(minSize, defaultBufferSize); + if (size > maxBufferSize) { + size = maxBufferSize; + } + return new byte[size]; + } + + /** + * Return a buffer to the pool for reuse. + * + *

    If the pool is full or the buffer is larger than maxPoolableSize, it's discarded. + * + * @param buffer the buffer to return (may be null, which is ignored) + */ + public void release(byte[] buffer) { + if (buffer == null) { + return; + } + if (buffer.length > maxPoolableSize) { + // Don't pool very large buffers; let GC handle them. + return; + } + + while (true) { + int currentTop = top.get(); + if (currentTop >= capacity) { + // Pool is full; drop the buffer. + return; + } + + if (top.compareAndSet(currentTop, currentTop + 1)) { + // We now "own" this slot; publish buffer with a volatile write. + stack.set(currentTop, buffer); + return; + } + // Lost the race, retry (or eventually see pool as full and drop). + } + } + + /** + * Get the current number of buffers in the pool. + * + * @return current pool size (approximate under contention) + */ + public int size() { + return top.get(); + } + + /** + * Clear all buffers from the pool. + * + *

    Best-effort, not strictly atomic wrt concurrent borrows/releases, + * but good enough for typical "connection shutdown" usage. + */ + public void clear() { + int n = top.getAndSet(0); + int limit = Math.min(n, capacity); + for (int i = 0; i < limit; i++) { + stack.set(i, null); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java index b37255960..e5ca769be 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java @@ -13,7 +13,6 @@ import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; -import java.util.function.IntFunction; import software.amazon.smithy.java.logging.InternalLogger; /** @@ -40,11 +39,11 @@ final class H1ConnectionManager { * Try to acquire a pooled connection for the route. * * @param route the route - * @param poolFactory + * @param maxConnections max pooled connections for this route (used if pool doesn't exist) * @return a valid pooled connection, or null if none available */ - PooledConnection tryAcquire(Route route, IntFunction poolFactory) { - HostPool hostPool = pools.computeIfAbsent(route, k -> poolFactory.apply(0)); + PooledConnection tryAcquire(Route route, int maxConnections) { + HostPool hostPool = pools.computeIfAbsent(route, k -> new HostPool(maxConnections)); PooledConnection pooled; while ((pooled = hostPool.poll()) != null) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java index 84a1308e5..48249cc8f 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java @@ -8,16 +8,28 @@ import java.io.IOException; import java.util.List; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BiConsumer; import software.amazon.smithy.java.http.client.h2.H2Connection; /** * Manages HTTP/2 connections for multiplexing. + * + *

    Uses a per-route state object containing a lock and volatile connection array. + * Fast path (acquire existing connection) requires no locking - just a volatile read + * and array scan. Slow path (create new connection) synchronizes on per-route lock. */ final class H2ConnectionManager { - private final ConcurrentHashMap> connections = new ConcurrentHashMap<>(); - private final ConcurrentHashMap locks = new ConcurrentHashMap<>(); + + /** + * Per-route state: synchronize on this object for mutations, volatile array for lock-free reads. + */ + private static final class RouteState { + volatile H2Connection[] conns = new H2Connection[0]; + } + + private static final H2Connection[] EMPTY = new H2Connection[0]; + + private final ConcurrentHashMap routes = new ConcurrentHashMap<>(); private final int streamsPerConnection; private final List listeners; private final ConnectionFactory connectionFactory; @@ -27,89 +39,121 @@ interface ConnectionFactory { H2Connection create(Route route) throws IOException; } - H2ConnectionManager(int streamsPerConnection, List listeners, ConnectionFactory connectionFactory) { + H2ConnectionManager( + int streamsPerConnection, + List listeners, + ConnectionFactory connectionFactory + ) { this.streamsPerConnection = streamsPerConnection; this.listeners = listeners; this.connectionFactory = connectionFactory; } + private RouteState stateFor(Route route) { + return routes.computeIfAbsent(route, r -> new RouteState()); + } + /** * Acquire an H2 connection for the route, creating one if needed. + * + *

    Connection creation happens OUTSIDE the synchronized block to prevent deadlock. + * The deadlock scenario: Thread A holds RouteState lock while creating a connection, + * which blocks waiting for a permit. Thread B tries to release a permit but first + * needs to unregister, which requires the RouteState lock held by A. */ H2Connection acquire(Route route) throws IOException { - // Fast path: find existing connection with capacity - H2Connection conn = tryAcquire(route); + RouteState state = stateFor(route); + + // Fast path: snapshot of current connections, no locking + H2Connection conn = tryAcquire(state.conns); if (conn != null) { notifyAcquire(conn, true); return conn; } - // Slow path: need new connection, serialize per route - synchronized (locks.computeIfAbsent(route, k -> new Object())) { - // Double-check with strict limit - H2Connection rechecked = tryAcquireUnderLimit(route); + // Slow path: check existing under lock, but DON'T create while holding lock + synchronized (state) { + H2Connection[] snapshot = state.conns; + H2Connection rechecked = tryAcquireUnderLimit(snapshot); if (rechecked != null) { notifyAcquire(rechecked, true); return rechecked; } - - // Create new connection - System.out.println("Making new h2 connection"); - H2Connection newConn = connectionFactory.create(route); - register(route, newConn); - notifyAcquire(newConn, false); - return newConn; } - } - private void notifyAcquire(H2Connection conn, boolean reused) { - for (ConnectionPoolListener listener : listeners) { - listener.onAcquire(conn, reused); + // Create new connection OUTSIDE the lock to avoid deadlock. Note: Multiple threads may race to create + // connections for the same route. This is acceptable: we'd rather over-create slightly than deadlock. + H2Connection newConn = connectionFactory.create(route); + + // Register under lock + synchronized (state) { + H2Connection[] cur = state.conns; + H2Connection[] next = new H2Connection[cur.length + 1]; + System.arraycopy(cur, 0, next, 0, cur.length); + next[cur.length] = newConn; + state.conns = next; } + + notifyAcquire(newConn, false); + return newConn; } /** - * Find a reusable connection for the route, or null if none available. + * Find a reusable connection, preferring low stream count. * - *

    Prefers connections with low stream count to spread load. + *

    Single pass: tracks best candidate (lowest active streams under limit), + * falls back to any valid connection if none under limit. + * + *

    Note: getActiveStreamCountIfAccepting() checks active state, write errors, and muxer capacity, + * returning the stream count in a single call to avoid redundant atomic reads. + * Socket health is monitored by the reader thread, so separate validateForReuse() is not + * needed in this hot path. */ - private H2Connection tryAcquire(Route route) { - List conns = connections.get(route); - if (conns == null) { - return null; - } + private H2Connection tryAcquire(H2Connection[] conns) { + H2Connection best = null; + int bestActive = Integer.MAX_VALUE; - // Prefer low stream count (spreads load) for (H2Connection conn : conns) { - if (conn.canAcceptMoreStreams() - && conn.getActiveStreamCount() < streamsPerConnection - && conn.validateForReuse()) { - return conn; + if (conn == null) { + continue; } - } - // Fall back to any available - for (H2Connection conn : conns) { - if (conn.canAcceptMoreStreams() && conn.validateForReuse()) { - return conn; + int active = conn.getActiveStreamCountIfAccepting(); + if (active < 0) { + continue; + } + + if (active < streamsPerConnection) { + // Prefer lowest active count + if (active < bestActive) { + best = conn; + bestActive = active; + if (active == 0) { + break; // Can't do better than idle + } + } + } else if (best == null) { + // Fallback: any valid connection + best = conn; } } - return null; + return best; } /** - * Find a connection under the soft limit, or null. + * Find a connection strictly under the soft limit. */ - private H2Connection tryAcquireUnderLimit(Route route) { - List conns = connections.get(route); - if (conns != null) { - for (H2Connection conn : conns) { - if (conn.canAcceptMoreStreams() - && conn.getActiveStreamCount() < streamsPerConnection - && conn.validateForReuse()) { - return conn; - } + private H2Connection tryAcquireUnderLimit(H2Connection[] conns) { + for (H2Connection conn : conns) { + if (conn == null) { + continue; + } + // getActiveStreamCountIfAccepting() checks: active, writeError, muxer capacity + // and returns the count in a single call to avoid redundant atomic reads + int active = conn.getActiveStreamCountIfAccepting(); + if (active >= 0 && active < streamsPerConnection) { + return conn; } } return null; @@ -119,16 +163,45 @@ private H2Connection tryAcquireUnderLimit(Route route) { * Register a new connection for the route. */ void register(Route route, H2Connection conn) { - connections.computeIfAbsent(route, k -> new CopyOnWriteArrayList<>()).add(conn); + RouteState state = stateFor(route); + synchronized (state) { + H2Connection[] cur = state.conns; + H2Connection[] next = new H2Connection[cur.length + 1]; + System.arraycopy(cur, 0, next, 0, cur.length); + next[cur.length] = conn; + state.conns = next; + } } /** * Unregister a connection from the route. */ void unregister(Route route, H2Connection conn) { - List conns = connections.get(route); - if (conns != null) { - conns.remove(conn); + RouteState state = routes.get(route); + if (state == null) { + return; + } + synchronized (state) { + H2Connection[] cur = state.conns; + int n = cur.length; + int idx = -1; + for (int i = 0; i < n; i++) { + if (cur[i] == conn) { + idx = i; + break; + } + } + if (idx < 0) { + return; + } + if (n == 1) { + state.conns = EMPTY; + return; + } + H2Connection[] next = new H2Connection[n - 1]; + System.arraycopy(cur, 0, next, 0, idx); + System.arraycopy(cur, idx + 1, next, idx, n - idx - 1); + state.conns = next; } } @@ -136,16 +209,49 @@ void unregister(Route route, H2Connection conn) { * Remove dead or exhausted connections for the route. */ void cleanupDead(Route route, BiConsumer onRemove) { - List conns = connections.get(route); - if (conns != null) { - conns.removeIf(conn -> { + RouteState state = routes.get(route); + if (state == null) { + return; + } + + H2Connection[] cur = state.conns; + + // Quick check without lock - if all look healthy, skip + boolean anyDead = false; + for (H2Connection conn : cur) { + if (conn != null && (!conn.canAcceptMoreStreams() || !conn.isActive())) { + anyDead = true; + break; + } + } + if (!anyDead) { + return; + } + + // Slow path: actually clean up under lock + synchronized (state) { + cur = state.conns; // Re-read under lock + int n = cur.length; + H2Connection[] tmp = new H2Connection[n]; + int w = 0; + for (H2Connection conn : cur) { + if (conn == null) { + continue; + } if (!conn.canAcceptMoreStreams() || !conn.isActive()) { - CloseReason reason = conn.isActive() ? CloseReason.EVICTED : CloseReason.UNEXPECTED_CLOSE; + CloseReason reason = conn.isActive() + ? CloseReason.EVICTED + : CloseReason.UNEXPECTED_CLOSE; onRemove.accept(conn, reason); - return true; + } else { + tmp[w++] = conn; } - return false; - }); + } + if (w != n) { + H2Connection[] next = new H2Connection[w]; + System.arraycopy(tmp, 0, next, 0, w); + state.conns = next; + } } } @@ -153,20 +259,81 @@ void cleanupDead(Route route, BiConsumer onRemove) { * Clean up dead connections for all routes. */ void cleanupAllDead(BiConsumer onRemove) { - for (Route route : connections.keySet()) { + for (Route route : routes.keySet()) { cleanupDead(route, onRemove); } } + /** + * Clean up idle connections that have no active streams and have been idle + * longer than the specified timeout. + * + * @param maxIdleTimeNanos maximum idle time in nanoseconds + * @param onRemove callback for removed connections + * @return number of connections removed + */ + int cleanupIdle(long maxIdleTimeNanos, BiConsumer onRemove) { + int removed = 0; + for (RouteState state : routes.values()) { + H2Connection[] cur = state.conns; + + // Quick check without lock - if none look idle, skip + boolean anyIdle = false; + for (H2Connection conn : cur) { + if (conn != null && conn.getIdleTimeNanos() > maxIdleTimeNanos) { + anyIdle = true; + break; + } + } + if (!anyIdle) { + continue; + } + + // Slow path: clean up under lock + synchronized (state) { + cur = state.conns; // Re-read under lock + int n = cur.length; + H2Connection[] tmp = new H2Connection[n]; + int w = 0; + for (H2Connection conn : cur) { + if (conn == null) { + continue; + } + if (conn.getIdleTimeNanos() > maxIdleTimeNanos) { + onRemove.accept(conn, CloseReason.IDLE_TIMEOUT); + removed++; + } else { + tmp[w++] = conn; + } + } + if (w != n) { + H2Connection[] next = new H2Connection[w]; + System.arraycopy(tmp, 0, next, 0, w); + state.conns = next; + } + } + } + return removed; + } + /** * Close all connections. */ void closeAll(BiConsumer onClose) { - for (List conns : connections.values()) { - for (H2Connection conn : conns) { - onClose.accept(conn, CloseReason.POOL_SHUTDOWN); + for (RouteState state : routes.values()) { + H2Connection[] snapshot = state.conns; + for (H2Connection conn : snapshot) { + if (conn != null) { + onClose.accept(conn, CloseReason.POOL_SHUTDOWN); + } } } - connections.clear(); + routes.clear(); + } + + private void notifyAcquire(H2Connection conn, boolean reused) { + for (ConnectionPoolListener listener : listeners) { + listener.onAcquire(conn, reused); + } } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java index 39efbc4ca..a0078de40 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java @@ -102,7 +102,7 @@ private HttpConnection connectToAddress(InetAddress address, Route route, List Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) ms; + } + private static void closeQuietly(Socket socket) { try { socket.close(); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java index 0b10a429f..60a43f696 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java @@ -129,13 +129,13 @@ public final class HttpConnectionPool implements ConnectionPool { // Soft limit on streams per connection before creating a new one. // Server's MAX_CONCURRENT_STREAMS is the hard limit; this spreads load before hitting it. - private static final int STREAMS_PER_CONNECTION = 4096; + private static final int STREAMS_PER_CONNECTION = 1024; private final int defaultMaxConnectionsPerRoute; private final Map perHostLimits; private final int maxTotalConnections; - private final long maxIdleTimeNanos; // Cached to avoid Duration.toNanos() in hot path private final long acquireTimeoutMs; // Timeout for acquiring a connection when pool is exhausted + private final long maxIdleTimeNanos; // Max idle time before closing connections private final HttpVersionPolicy versionPolicy; private final HttpConnectionFactory connectionFactory; @@ -159,6 +159,7 @@ public final class HttpConnectionPool implements ConnectionPool { this.defaultMaxConnectionsPerRoute = builder.maxConnectionsPerRoute; this.perHostLimits = Map.copyOf(builder.perHostLimits); this.maxTotalConnections = builder.maxTotalConnections; + // Cached to avoid Duration.toNanos() in hot path this.maxIdleTimeNanos = builder.maxIdleTime.toNanos(); this.acquireTimeoutMs = builder.acquireTimeout.toMillis(); this.versionPolicy = builder.versionPolicy; @@ -184,7 +185,7 @@ public final class HttpConnectionPool implements ConnectionPool { dnsResolver, builder.socketFactory); - this.h1Manager = new H1ConnectionManager(maxIdleTimeNanos); + this.h1Manager = new H1ConnectionManager(this.maxIdleTimeNanos); this.connectionPermits = new Semaphore(builder.maxTotalConnections, false); this.listeners = List.copyOf(builder.listeners); this.h2Manager = new H2ConnectionManager(STREAMS_PER_CONNECTION, listeners, this::onNewH2Connection); @@ -216,9 +217,7 @@ private HttpConnection acquireH1(Route route) throws IOException { int maxConns = getMaxConnectionsForRoute(route); // Quick check: try to reuse a pooled connection - H1ConnectionManager.PooledConnection pooled = h1Manager.tryAcquire( - route, - ignored -> new H1ConnectionManager.HostPool(maxConns)); + H1ConnectionManager.PooledConnection pooled = h1Manager.tryAcquire(route, maxConns); if (pooled != null) { notifyAcquire(pooled.connection(), true); @@ -229,38 +228,66 @@ private HttpConnection acquireH1(Route route) throws IOException { acquirePermit(); // Create new HTTP/1.1 connection + HttpConnection conn = null; + boolean success = false; try { - HttpConnection conn = connectionFactory.create(route); + conn = connectionFactory.create(route); notifyConnected(conn); notifyAcquire(conn, false); + success = true; return conn; - } catch (IOException | RuntimeException e) { - connectionPermits.release(); - throw e; + } catch (Throwable e) { + if (e instanceof IOException) { + throw (IOException) e; + } else if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else { + throw new RuntimeException(e); + } + } finally { + if (!success) { + connectionPermits.release(); + if (conn != null) { + closeConnection(conn); + } + } } } // Called by H2ConnectionManager when a new connection is needed. private H2Connection onNewH2Connection(Route route) throws IOException { - // Clean up dead connections first - h2Manager.cleanupDead(route, this::closeAndReleasePermit); + // Note: cleanupDead was removed from here - it caused lock contention under load. + // Background cleanup thread handles dead connection removal every 30 seconds. // Block on global capacity acquirePermit(); + HttpConnection conn = null; + boolean success = false; try { - HttpConnection conn = connectionFactory.create(route); + conn = connectionFactory.create(route); notifyConnected(conn); if (conn instanceof H2Connection h2conn) { + success = true; return h2conn; } // ALPN negotiated HTTP/1.1 instead of H2 - shouldn't happen with H2C_PRIOR_KNOWLEDGE - closeConnection(conn); - connectionPermits.release(); throw new IOException("Expected H2 connection but got " + conn.httpVersion()); - } catch (IOException | RuntimeException e) { - connectionPermits.release(); - throw e; + } catch (Throwable e) { + if (e instanceof IOException) { + throw (IOException) e; + } else if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else { + throw new RuntimeException(e); + } + } finally { + if (!success) { + connectionPermits.release(); + if (conn != null) { + closeConnection(conn); + } + } } } @@ -396,23 +423,20 @@ private int getMaxConnectionsForRoute(Route route) { return defaultMaxConnectionsPerRoute; } - String host = route.host(); + Integer limit = perHostLimits.get(route.authority()); + if (limit != null) { + return limit; + } - // Check with port first (more specific) + // For non-default ports, also check host-only limit as a fallback + // (e.g., api.example.com:8080 falls back to api.example.com limit) if (route.port() != 80 && route.port() != 443) { - String hostWithPort = host + ":" + route.port(); - Integer limit = perHostLimits.get(hostWithPort); + limit = perHostLimits.get(route.host()); if (limit != null) { return limit; } } - // Check without port (less specific) - Integer limit = perHostLimits.get(host); - if (limit != null) { - return limit; - } - // Use default return defaultMaxConnectionsPerRoute; } @@ -475,11 +499,9 @@ private void notifyClosed(HttpConnection connection, CloseReason reason) { *

    For HTTP/2 connections, removes connections that: *

      *
    • Are no longer active or can't accept more streams
    • + *
    • Have no active streams and have been idle longer than {@code maxIdleTime}
    • *
    * - *

    Note: {@code maxIdleTime} currently only applies to HTTP/1.1 connections. - * HTTP/2 connections remain open until they become unhealthy. - * *

    Runs on a virtual thread, so blocking is cheap. */ private void cleanupIdleConnections() { @@ -496,6 +518,10 @@ private void cleanupIdleConnections() { // Clean up unhealthy HTTP/2 connections h2Manager.cleanupAllDead(this::closeAndReleasePermit); + // Clean up idle HTTP/2 connections (no active streams and idle too long) + // Note: closeAndReleasePermit already releases the permit + h2Manager.cleanupIdle(maxIdleTimeNanos, this::closeAndReleasePermit); + } catch (InterruptedException e) { break; } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/Route.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/Route.java index 2fc25f988..63cc8a6f0 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/Route.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/Route.java @@ -45,6 +45,7 @@ public final class Route { private final int port; private final ProxyConfiguration proxy; private final int cachedHashCode; + private final String authority; /** * Create a new Route. @@ -83,6 +84,10 @@ public Route(String scheme, String host, int port, ProxyConfiguration proxy) { h = 31 * h + this.port; h = 31 * h + (this.proxy != null ? this.proxy.hashCode() : 0); this.cachedHashCode = h; + + // Pre-compute authority to avoid string allocation in hot path + int defaultPort = "https".equals(scheme) ? 443 : 80; + this.authority = (port == defaultPort) ? this.host : this.host + ":" + port; } /** @@ -106,6 +111,15 @@ public int port() { return port; } + /** + * Get the authority (host:port or just host for default ports). + * + * @return the authority string + */ + public String authority() { + return authority; + } + /** * @return the proxy configuration, or null if direct connection */ diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java index 20aa06a61..36bc0d4da 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java @@ -246,7 +246,7 @@ private void readTrailers() throws IOException { ModifiableHttpHeaders parsedTrailers = HttpHeaders.ofModifiable(); int len; while ((len = delegate.readLine(lineBuffer, MAX_LINE_LENGTH)) > 0) { - String name = HttpUtils.parseHeaderLine(lineBuffer, len, parsedTrailers); + String name = H1Utils.parseHeaderLine(lineBuffer, len, parsedTrailers); if (name == null) { throw new IOException("Invalid trailer line: " + new String(lineBuffer, 0, len, StandardCharsets.US_ASCII)); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java index 3e2c36d02..d665d7d01 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java @@ -475,7 +475,7 @@ private void parseStatusAndHeaders(int code, UnsyncBufferedInputStream in) throw + " exceeds maximum of " + MAX_RESPONSE_HEADER_COUNT); } - String name = HttpUtils.parseHeaderLine(responseLineBuffer, lineLen, headers); + String name = H1Utils.parseHeaderLine(responseLineBuffer, lineLen, headers); if (name == null) { throw new IOException("Invalid header line: " + new String(responseLineBuffer, 0, lineLen, StandardCharsets.US_ASCII)); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Utils.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Utils.java new file mode 100644 index 000000000..1ba3ccaec --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Utils.java @@ -0,0 +1,66 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import software.amazon.smithy.java.http.api.HeaderNames; +import software.amazon.smithy.java.http.api.ModifiableHttpHeaders; + +/** + * HTTP/1.1 parsing utilities. + * + *

    Uses {@link HeaderNames} for header name normalization. + */ +final class H1Utils { + + private H1Utils() {} + + /** + * Parse a header line and add it to the headers collection. + * + * @param buf byte buffer containing header line + * @param len length of header line (excluding CRLF) + * @param headers collection to add the parsed header to + * @return the interned header name, or null if line is malformed (no colon) + */ + static String parseHeaderLine(byte[] buf, int len, ModifiableHttpHeaders headers) { + // Find colon + int colon = -1; + for (int i = 0; i < len; i++) { + if (buf[i] == ':') { + colon = i; + break; + } + } + + if (colon <= 0) { + return null; + } + + // Normalize header name using centralized registry + String name = HeaderNames.canonicalize(buf, 0, colon); + + // Find value bounds, skip leading/trailing OWS (space or tab per RFC 9110) + int valueStart = colon + 1; + int valueEnd = len; + while (valueStart < valueEnd && isOWS(buf[valueStart])) { + valueStart++; + } + while (valueEnd > valueStart && isOWS(buf[valueEnd - 1])) { + valueEnd--; + } + + String value = new String(buf, valueStart, valueEnd - valueStart, java.nio.charset.StandardCharsets.US_ASCII); + headers.addHeader(name, value); + return name; + } + + /** + * Check if byte is optional whitespace (OWS) per RFC 9110: SP or HTAB. + */ + private static boolean isOWS(byte b) { + return b == ' ' || b == '\t'; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/HttpUtils.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/HttpUtils.java deleted file mode 100644 index cc47c3752..000000000 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/HttpUtils.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.h1; - -import java.nio.charset.StandardCharsets; -import software.amazon.smithy.java.http.api.ModifiableHttpHeaders; - -/** - * Interned HTTP header names to avoid String allocation for common headers. - * - *

    Uses length-based switching and case-insensitive byte comparison to - * return pre-allocated String constants for well-known headers. - */ -final class HttpUtils { - - private static final String[] GROUP_4 = {"date", "vary", "etag"}; - private static final String[] GROUP_6 = {"server"}; - private static final String[] GROUP_7 = {"trailer", "expires", "upgrade"}; - private static final String[] GROUP_8 = {"location"}; - private static final String[] GROUP_10 = {"connection", "keep-alive", "set-cookie"}; - private static final String[] GROUP_12 = {"content-type"}; - private static final String[] GROUP_13 = {"cache-control", "last-modified", "content-range", "accept-ranges"}; - private static final String[] GROUP_14 = {"content-length"}; - private static final String[] GROUP_16 = { - "content-encoding", - "x-amzn-requestid", - "x-amz-request-id", - "www-authenticate", - "proxy-connection" - }; - private static final String[] GROUP_17 = {"transfer-encoding"}; - private static final String[] GROUP_18 = {"proxy-authenticate"}; - - private HttpUtils() {} - - /** - * Returns an interned String for common header names, or creates a new String for unknown headers. - * - * @param buf byte buffer containing header name - * @param start start offset in buffer - * @param len length of header name - * @return interned String for known headers, new String for unknown - */ - static String internHeader(byte[] buf, int start, int len) { - return switch (len) { - case 4 -> match(buf, start, len, GROUP_4); - case 6 -> match(buf, start, len, GROUP_6); - case 7 -> match(buf, start, len, GROUP_7); - case 8 -> match(buf, start, len, GROUP_8); - case 10 -> match(buf, start, len, GROUP_10); - case 12 -> match(buf, start, len, GROUP_12); - case 13 -> match(buf, start, len, GROUP_13); - case 14 -> match(buf, start, len, GROUP_14); - case 16 -> match(buf, start, len, GROUP_16); - case 17 -> match(buf, start, len, GROUP_17); - case 18 -> match(buf, start, len, GROUP_18); - default -> new String(buf, start, len, StandardCharsets.US_ASCII); - }; - } - - private static String match(byte[] buf, int start, int len, String[] group) { - for (String candidate : group) { - if (equalsIgnoreCase(buf, start, candidate)) { - return candidate; - } - } - return new String(buf, start, len, StandardCharsets.US_ASCII); - } - - private static boolean equalsIgnoreCase(byte[] buf, int start, String expected) { - for (int i = 0; i < expected.length(); i++) { - byte b = buf[start + i]; - // Convert to lowercase if uppercase ASCII letter - if (b >= 'A' && b <= 'Z') { - b += 32; - } - if (b != expected.charAt(i)) { - return false; - } - } - return true; - } - - /** - * Parse a header line and add it to the headers collection. - * - * @param buf byte buffer containing header line - * @param len length of header line (excluding CRLF) - * @param headers collection to add the parsed header to - * @return the interned header name, or null if line is malformed (no colon) - */ - static String parseHeaderLine(byte[] buf, int len, ModifiableHttpHeaders headers) { - // Find colon - int colon = -1; - for (int i = 0; i < len; i++) { - if (buf[i] == ':') { - colon = i; - break; - } - } - - if (colon <= 0) { - return null; - } - - // Intern header name - String name = internHeader(buf, 0, colon); - - // Find value bounds, skip leading/trailing OWS (space or tab per RFC 9110) - int valueStart = colon + 1; - int valueEnd = len; - while (valueStart < valueEnd && isOWS(buf[valueStart])) { - valueStart++; - } - while (valueEnd > valueStart && isOWS(buf[valueEnd - 1])) { - valueEnd--; - } - - String value = new String(buf, valueStart, valueEnd - valueStart, StandardCharsets.US_ASCII); - headers.addHeader(name, value); - return name; - } - - /** - * Check if byte is optional whitespace (OWS) per RFC 9110: SP or HTAB. - */ - private static boolean isOWS(byte b) { - return b == ' ' || b == '\t'; - } -} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java new file mode 100644 index 000000000..cc58cd8f4 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java @@ -0,0 +1,81 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +/** + * HTTP/2 flow control window backed by a Semaphore. + * + *

    Wraps acquire/release semantics for flow control with Virtual Thread-friendly + * blocking. Uses an unfair semaphore for throughput over ordering. + */ +final class FlowControlWindow { + + private final Semaphore permits; + + /** + * Create a flow control window. + * + * @param initialWindow the initial window size (e.g., 65535 for HTTP/2 default) + */ + FlowControlWindow(int initialWindow) { + // Use unfair semaphore for better throughput (no FIFO ordering overhead) + this.permits = new Semaphore(initialWindow, false); + } + + /** + * Acquire permits with a timeout. + * + * @param bytes number of bytes to acquire + * @param timeout maximum time to wait + * @param unit time unit for timeout + * @return true if permits acquired, false if timeout expired + * @throws InterruptedException if interrupted while waiting + */ + boolean tryAcquire(int bytes, long timeout, TimeUnit unit) throws InterruptedException { + return permits.tryAcquire(bytes, timeout, unit); + } + + /** + * Release permits back to the window. + * + * @param bytes number of bytes to release + */ + void release(int bytes) { + if (bytes > 0) { + permits.release(bytes); + } + } + + /** + * Get the current available window size. + * + * @return available bytes in the window + */ + int available() { + return permits.availablePermits(); + } + + /** + * Adjust the window size (e.g., when SETTINGS changes initial window). + * + *

    This can increase or decrease the window. If decreasing and the new window would be negative, this may + * cause subsequent acquires to block until the window recovers. + * + * @param delta change in window size (positive or negative) + */ + void adjust(int delta) { + if (delta > 0) { + permits.release(delta); + } else if (delta < 0) { + // Reducing window: try to acquire the permits. If not enough available, the window goes "into debt" + // and future acquires will block longer. + var _ignored = permits.tryAcquire(-delta); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java index 66dcb9ef1..6c24106e8 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java @@ -19,6 +19,7 @@ import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_ACK; import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_END_HEADERS; import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_END_STREAM; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_PADDED; import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_DATA; import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_GOAWAY; import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_HEADERS; @@ -42,11 +43,8 @@ import java.time.Duration; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; -import software.amazon.smithy.java.http.api.HttpHeaders; import software.amazon.smithy.java.http.api.HttpRequest; import software.amazon.smithy.java.http.api.HttpVersion; import software.amazon.smithy.java.http.client.HttpExchange; @@ -54,6 +52,7 @@ import software.amazon.smithy.java.http.client.UnsyncBufferedOutputStream; import software.amazon.smithy.java.http.client.connection.HttpConnection; import software.amazon.smithy.java.http.client.connection.Route; +import software.amazon.smithy.java.http.client.h2.hpack.HeaderField; import software.amazon.smithy.java.http.client.h2.hpack.HpackDecoder; import software.amazon.smithy.java.logging.InternalLogger; @@ -62,7 +61,7 @@ * *

    This implementation manages an HTTP/2 connection over a single TCP socket * with support for multiple concurrent streams. A background reader thread - * dispatches incoming frames to the appropriate stream handlers. + * dispatches incoming frames to the multiplexer. * *

    Connection Lifecycle

    *
      @@ -76,13 +75,10 @@ *

      Thread Safety

      *

      This class is thread-safe. Multiple virtual threads can create * concurrent exchanges on the same connection. Frame writes are serialized - * via a dedicated writer thread with queue, and frame reads are handled by a + * via the muxer's writer thread, and frame reads are handled by a * dedicated reader thread. */ -public final class H2Connection implements HttpConnection, H2StreamWriter.StreamManager { - /** - * Internal connection state. - */ +public final class H2Connection implements HttpConnection, H2Muxer.ConnectionCallback { private enum State { CONNECTED, SHUTTING_DOWN, @@ -90,80 +86,56 @@ private enum State { } private static final InternalLogger LOGGER = InternalLogger.getLogger(H2Connection.class); + private static final byte[] EMPTY_PAYLOAD = new byte[0]; + private static final int SETTINGS_TIMEOUT_MS = 10_000; + private static final int GRACEFUL_SHUTDOWN_MS = 1000; private final Socket socket; private final UnsyncBufferedOutputStream socketOut; private final Route route; private final H2FrameCodec frameCodec; - - // Combined encoder/writer - handles both HPACK encoding AND frame writing - // All socket writes are serialized through this single thread - private final H2StreamWriter streamWriter; - - // HPACK decoder for responses (only accessed by reader thread - no synchronization needed) + private final H2Muxer muxer; private final HpackDecoder hpackDecoder; + private final Thread readerThread; + private final long readTimeoutMs; + private final long writeTimeoutMs; - // Connection settings (ours and peer's) + // Connection settings from peer private volatile int remoteMaxFrameSize = DEFAULT_MAX_FRAME_SIZE; private volatile int remoteInitialWindowSize = DEFAULT_INITIAL_WINDOW_SIZE; private volatile int remoteMaxConcurrentStreams = DEFAULT_MAX_CONCURRENT_STREAMS; private volatile int remoteHeaderTableSize = DEFAULT_HEADER_TABLE_SIZE; - // Server's limit for header list size we send (default: unlimited per RFC 9113) private volatile int remoteMaxHeaderListSize = Integer.MAX_VALUE; - // Flow control - use AtomicInteger for thread-safe updates - private final AtomicInteger connectionSendWindow = new AtomicInteger(DEFAULT_INITIAL_WINDOW_SIZE); - private volatile int connectionRecvWindow = DEFAULT_INITIAL_WINDOW_SIZE; - - // Our limit for received header list size (not from server SETTINGS) - private static final int DEFAULT_MAX_HEADER_LIST_SIZE = 8192; - - // Stream management - multiplexed! - private final ConcurrentHashMap activeStreams = new ConcurrentHashMap<>(); - private final AtomicInteger activeStreamCount = new AtomicInteger(0); - private volatile int lastStreamId = 0; - - // Background reader thread - private final Thread readerThread; - private volatile Throwable readerError; + // Connection receive window (send window is managed by muxer). Only accessed by reader thread. + private int connectionRecvWindow = DEFAULT_INITIAL_WINDOW_SIZE; // Connection state private volatile State state = State.CONNECTED; private volatile boolean active = true; private volatile boolean goawayReceived = false; private volatile int goawayLastStreamId = Integer.MAX_VALUE; - - // Stream-level timeouts - private final Duration readTimeout; - private final Duration writeTimeout; + private volatile Throwable readerError; + // Track last activity time for idle timeout (nanos) + private volatile long lastActivityTimeNanos = System.nanoTime(); /** * Create an HTTP/2 connection from a connected socket. - * - *

      The socket must already be connected and TLS handshake completed (if applicable). - * This constructor sends the connection preface and negotiates settings. - * - * @param socket the connected socket - * @param route the connection route - * @param readTimeout timeout for waiting on response data - * @param writeTimeout timeout for waiting on flow control window - * @throws IOException if connection preface fails */ public H2Connection(Socket socket, Route route, Duration readTimeout, Duration writeTimeout) throws IOException { this.socket = socket; - // Use unsynchronized buffered streams (safe because reader/writer threads have exclusive access) var socketIn = new UnsyncBufferedInputStream(socket.getInputStream(), 8192); this.socketOut = new UnsyncBufferedOutputStream(socket.getOutputStream(), 8192); this.route = route; - this.readTimeout = readTimeout; - this.writeTimeout = writeTimeout; + this.readTimeoutMs = readTimeout.toMillis(); + this.writeTimeoutMs = writeTimeout.toMillis(); this.frameCodec = new H2FrameCodec(socketIn, socketOut, DEFAULT_MAX_FRAME_SIZE); - - // Initialize HPACK decoder for responses (only accessed by reader thread) this.hpackDecoder = new HpackDecoder(DEFAULT_HEADER_TABLE_SIZE); - // Perform connection preface BEFORE starting encoder thread - // (preface writes directly to frameCodec, encoder thread takes over after) + // Create muxer before connection preface (applyRemoteSettings needs it) + this.muxer = new H2Muxer(this, frameCodec, DEFAULT_HEADER_TABLE_SIZE, "h2-writer-" + route.host()); + + // Perform connection preface try { sendConnectionPreface(); receiveServerPreface(); @@ -172,33 +144,43 @@ public H2Connection(Socket socket, Route route, Duration readTimeout, Duration w throw new IOException("HTTP/2 connection preface failed", e); } - // Create combined encoder/writer AFTER connection preface - // This thread handles both HPACK encoding AND all frame writes - this.streamWriter = new H2StreamWriter( - this, - frameCodec, - DEFAULT_HEADER_TABLE_SIZE, - "h2-writer-" + route.host()); - - // Start background reader thread (dispatches incoming frames) + // Start background reader thread this.readerThread = Thread.ofVirtual() .name("h2-reader-" + route.host()) .start(this::readerLoop); } - /** - * Background reader loop - dispatches frames to streams. - */ + // ==================== ConnectionCallback implementation ==================== + + @Override + public boolean isAcceptingStreams() { + return state == State.CONNECTED && !goawayReceived; + } + + @Override + public int getRemoteMaxHeaderListSize() { + return remoteMaxHeaderListSize; + } + + // ==================== Reader Thread ==================== + private void readerLoop() { try { while (state == State.CONNECTED) { - H2FrameCodec.Frame frame = frameCodec.readFrame(); - if (frame == null) { - // EOF - connection closed by peer + H2FrameCodec.FrameHeader header = frameCodec.readFrameHeader(); + if (header == null) { break; } - dispatchFrame(frame); + // Update last activity time on every frame received + lastActivityTimeNanos = System.nanoTime(); + + if (header.type() == FRAME_TYPE_DATA) { + handleDataFrame(header); + } else { + H2FrameCodec.Frame frame = readNonDataFrame(header); + dispatchFrame(frame); + } } } catch (IOException e) { if (state == State.CONNECTED) { @@ -207,47 +189,88 @@ private void readerLoop() { LOGGER.debug("Reader thread error for {}: {}", route, e.getMessage()); } } finally { - // Stop the encoder immediately on connection failure - // (don't wait for graceful drain - connection is dead) - if (streamWriter != null) { - streamWriter.shutdownNow(); - } - - // Signal all active streams that connection is closing - for (H2Exchange exchange : activeStreams.values()) { - exchange.signalConnectionClosed(readerError); + if (muxer != null) { + muxer.shutdownNow(); } + muxer.onConnectionClosing(readerError); state = State.CLOSED; - - // Close socket to release resources promptly - // (don't wait for pool/caller to notice the connection is dead) try { socket.close(); - } catch (IOException ignored) { - // Best effort - socket may already be closed + } catch (IOException ignored) {} + } + } + + private void handleDataFrame(H2FrameCodec.FrameHeader header) throws IOException { + int streamId = header.streamId(); + int payloadLength = header.payloadLength(); + boolean endStream = header.hasFlag(FLAG_END_STREAM); + boolean padded = header.hasFlag(FLAG_PADDED); + + if (streamId == 0) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, "DATA frame must have non-zero stream ID"); + } + + H2Exchange exchange = muxer.getExchange(streamId); + + int padLength = 0; + int dataLength = payloadLength; + if (padded) { + if (payloadLength < 1) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, "Padded DATA frame too short"); + } + padLength = frameCodec.readByte(); + dataLength = payloadLength - 1 - padLength; + if (dataLength < 0) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, "Pad length " + padLength + " exceeds payload"); } } + + if (exchange != null) { + if (dataLength > 0) { + if (!exchange.ensureBufferSpace(dataLength)) { + throw new H2Exception(ERROR_FLOW_CONTROL_ERROR, + streamId, + "Exchange buffer overflow - flow control failure"); + } + frameCodec.readPayloadInto(exchange.getDataBuffer(), exchange.getWritePos(), dataLength); + exchange.commitWrite(dataLength, endStream); + consumeConnectionRecvWindow(dataLength); + } else if (endStream) { + exchange.commitWrite(0, true); + } + } else { + if (dataLength > 0) { + frameCodec.skipBytes(dataLength); + consumeConnectionRecvWindow(dataLength); + } + LOGGER.trace("Ignoring DATA frame for closed stream {}", streamId); + } + + if (padLength > 0) { + frameCodec.skipBytes(padLength); + } + } + + private H2FrameCodec.Frame readNonDataFrame(H2FrameCodec.FrameHeader header) throws IOException { + int length = header.payloadLength(); + byte[] payload; + if (length == 0) { + payload = EMPTY_PAYLOAD; + } else { + payload = new byte[length]; + frameCodec.readPayloadInto(payload, 0, length); + } + return new H2FrameCodec.Frame(header.type(), header.flags(), header.streamId(), payload, length); } - /** - * Dispatch a frame to the appropriate handler. - * - *

      Stream-level frames are converted to {@link StreamEvent}s and enqueued - * to the exchange. HPACK decoding happens here to ensure dynamic table - * consistency across all streams. - */ private void dispatchFrame(H2FrameCodec.Frame frame) throws IOException { int streamId = frame.streamId(); if (streamId == 0) { - // Connection-level frame handleConnectionFrame(frame); } else { - // Handle HEADERS frames that may need CONTINUATION if (frame.type() == FRAME_TYPE_HEADERS && !frame.hasFlag(FLAG_END_HEADERS)) { - // Read complete header block including CONTINUATION frames byte[] headerBlock = frameCodec.readHeaderBlock(frame); - // Create a synthetic frame with complete headers frame = new H2FrameCodec.Frame( FRAME_TYPE_HEADERS, frame.flags() | FLAG_END_HEADERS, @@ -256,133 +279,82 @@ private void dispatchFrame(H2FrameCodec.Frame frame) throws IOException { headerBlock.length); } - // Stream-level frame - H2Exchange exchange = activeStreams.get(streamId); + H2Exchange exchange = muxer.getExchange(streamId); if (exchange != null) { dispatchStreamFrame(exchange, frame, streamId); } else { - // Frame for unknown stream handleFrameForUnknownStream(frame, streamId); } } } - /** - * Handle frames received for streams not in our active set. - * - *

      Per RFC 9113 Section 5.1, after sending RST_STREAM we must be prepared to receive - * and ignore additional frames that were in-flight. This commonly occurs when we close - * a stream before fully reading the response - the server may have already sent DATA. - * - *

      For DATA frames: consume the connection recv window (for flow control) and ignore. - * For HEADERS frames: decode headers (to maintain HPACK state) and ignore. - * For other frames (WINDOW_UPDATE, RST_STREAM): silently ignore. - */ private void handleFrameForUnknownStream(H2FrameCodec.Frame frame, int streamId) throws IOException { if (frame.type() == FRAME_TYPE_DATA) { - // Consume connection-level flow control window for ignored DATA byte[] payload = frame.payload(); if (payload != null && payload.length > 0) { consumeConnectionRecvWindow(payload.length); } LOGGER.trace("Ignoring DATA frame for closed stream {}", streamId); } else if (frame.type() == FRAME_TYPE_HEADERS) { - // Must decode headers to keep HPACK decoder state in sync byte[] headerBlock = frame.payload(); if (headerBlock != null && headerBlock.length > 0) { decodeHeaders(headerBlock); } LOGGER.trace("Ignoring HEADERS frame for closed stream {}", streamId); } - // Other frame types (WINDOW_UPDATE, RST_STREAM) are silently ignored } - /** - * Dispatch a stream-level frame as a StreamEvent. - */ private void dispatchStreamFrame(H2Exchange exchange, H2FrameCodec.Frame frame, int streamId) throws IOException { switch (frame.type()) { case FRAME_TYPE_HEADERS -> { - // HPACK decoding MUST happen here in the reader thread to ensure - // dynamic table updates are processed in frame order across all streams. byte[] headerBlock = frame.payload(); - List decoded; + List decoded; if (headerBlock != null && headerBlock.length > 0) { decoded = decodeHeaders(headerBlock); } else { decoded = List.of(); } - boolean endStream = frame.hasFlag(FLAG_END_STREAM); - exchange.enqueueEvent(new StreamEvent.Headers(decoded, endStream)); + exchange.deliverHeaders(decoded, endStream); } - - case FRAME_TYPE_DATA -> { - byte[] payload = frame.payload(); - boolean endStream = frame.hasFlag(FLAG_END_STREAM); - - // Handle flow control: update connection receive window - if (payload != null && payload.length > 0) { - consumeConnectionRecvWindow(payload.length); - } - - // Create DataChunk event - StreamEvent.DataChunk chunk; - if (payload != null && payload.length > 0) { - chunk = new StreamEvent.DataChunk(payload, 0, payload.length, endStream); - } else if (endStream) { - chunk = StreamEvent.DataChunk.END; - } else { - chunk = StreamEvent.DataChunk.EMPTY; - } - - exchange.enqueueEvent(chunk); - } - case FRAME_TYPE_RST_STREAM -> { - // RST_STREAM: signal stream error so consumers fail fast int errorCode = frame.parseRstStream(); H2Exception error = new H2Exception(errorCode, streamId, "Stream reset by server: " + H2Constants.errorCodeName(errorCode)); exchange.signalStreamError(error); } - case FRAME_TYPE_WINDOW_UPDATE -> { - // WINDOW_UPDATE affects send window, not response reading int increment = frame.parseWindowUpdate(); exchange.updateStreamSendWindow(increment); } - case FRAME_TYPE_PUSH_PROMISE -> { - // We disable push, so this is a protocol error throw new H2Exception(ERROR_PROTOCOL_ERROR, "Received PUSH_PROMISE but server push is disabled"); } - default -> { - // Unknown frame types are ignored per spec } } } - /** - * Handle connection-level frames (stream ID 0). - */ private void handleConnectionFrame(H2FrameCodec.Frame frame) throws IOException { switch (frame.type()) { case FRAME_TYPE_SETTINGS -> { if (!frame.hasFlag(FLAG_ACK)) { applyRemoteSettings(frame); - // Submit SETTINGS ACK to writer thread - streamWriter.submitControlFrame(new H2StreamWriter.WorkItem.WriteSettingsAck()); + muxer.queueControlFrame(0, + H2Muxer.ControlFrameType.SETTINGS_ACK, + null, + writeTimeoutMs); } } case FRAME_TYPE_PING -> { if (!frame.hasFlag(FLAG_ACK)) { - // Submit PING ACK to writer thread - streamWriter.submitControlFrame(new H2StreamWriter.WorkItem.WritePing(frame.payload(), true)); + muxer.queueControlFrame(0, + H2Muxer.ControlFrameType.PING, + frame.payload(), + writeTimeoutMs); } } case FRAME_TYPE_GOAWAY -> { @@ -391,98 +363,60 @@ private void handleConnectionFrame(H2FrameCodec.Frame frame) throws IOException } case FRAME_TYPE_WINDOW_UPDATE -> { int increment = frame.parseWindowUpdate(); - int newWindow = connectionSendWindow.addAndGet(increment); - // Check for overflow per RFC 9113 (wrap-around to negative) - if (newWindow < 0) { - throw new H2Exception(ERROR_FLOW_CONTROL_ERROR, - "Connection send window overflow: " + newWindow); - } - // Wake up any streams waiting for send window - for (H2Exchange exchange : activeStreams.values()) { - exchange.signalWindowUpdate(); - } + muxer.releaseConnectionWindow(increment); } default -> { - // Ignore unknown connection-level frames } } } - /** - * Send client connection preface. - * RFC 9113 Section 3.4 - */ + // ==================== Connection Preface ==================== + private void sendConnectionPreface() throws IOException { - // 1. Send magic string socketOut.write(CONNECTION_PREFACE); - - // 2. Send SETTINGS frame with our preferences frameCodec.writeSettings( SETTINGS_MAX_CONCURRENT_STREAMS, - 100, // We can handle 100 concurrent streams + 100, SETTINGS_INITIAL_WINDOW_SIZE, - 65535, // Default 64KB window + 65535, SETTINGS_MAX_FRAME_SIZE, - 16384, // Default 16KB frames + 16384, SETTINGS_ENABLE_PUSH, - 0 // Disable server push - ); + 0); frameCodec.flush(); } - // Timeout for receiving SETTINGS frame during connection setup (RFC 9113 Section 6.5.3) - private static final int SETTINGS_TIMEOUT_MS = 10_000; // 10 seconds - - /** - * Receive and process server preface. - * - *

      RFC 9113 Section 6.5.3 recommends that implementations provide a way to - * bound the time within which a response to a SETTINGS frame is expected. - */ private void receiveServerPreface() throws IOException { - // Set socket timeout for SETTINGS frame (RFC 9113 Section 6.5.3) int originalTimeout = socket.getSoTimeout(); try { socket.setSoTimeout(SETTINGS_TIMEOUT_MS); - // Read server's SETTINGS frame H2FrameCodec.Frame frame; try { frame = frameCodec.readFrame(); } catch (SocketTimeoutException e) { - throw new H2Exception(ERROR_SETTINGS_TIMEOUT, - "Timeout waiting for server SETTINGS frame"); + throw new H2Exception(ERROR_SETTINGS_TIMEOUT, "Timeout waiting for server SETTINGS frame"); } if (frame == null) { throw new IOException("Connection closed before receiving server SETTINGS"); } - if (frame.type() != FRAME_TYPE_SETTINGS) { throw new H2Exception(ERROR_PROTOCOL_ERROR, "Expected SETTINGS frame, got " + H2Constants.frameTypeName(frame.type())); } - if (frame.hasFlag(FLAG_ACK)) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, - "First SETTINGS frame must not be ACK"); + throw new H2Exception(ERROR_PROTOCOL_ERROR, "First SETTINGS frame must not be ACK"); } - // Apply server settings applyRemoteSettings(frame); - - // Send SETTINGS ACK frameCodec.writeSettingsAck(); frameCodec.flush(); } finally { - // Restore original timeout (usually 0 = infinite for the reader loop) socket.setSoTimeout(originalTimeout); } } - /** - * Apply settings received from peer. - */ private void applyRemoteSettings(H2FrameCodec.Frame frame) throws IOException { int[] settings = frame.parseSettings(); for (int i = 0; i < settings.length; i += 2) { @@ -492,243 +426,161 @@ private void applyRemoteSettings(H2FrameCodec.Frame frame) throws IOException { switch (id) { case SETTINGS_HEADER_TABLE_SIZE: remoteHeaderTableSize = value; - // Update encoder table size (applied on next encode) - streamWriter.setMaxTableSize(value); + muxer.setMaxTableSize(value); break; case SETTINGS_ENABLE_PUSH: - // We disable push, ignore server's preference break; case SETTINGS_MAX_CONCURRENT_STREAMS: remoteMaxConcurrentStreams = value; + muxer.onSettingsReceived(value, remoteInitialWindowSize, remoteMaxFrameSize); break; case SETTINGS_INITIAL_WINDOW_SIZE: - // Value is unsigned 32-bit in protocol; negative means > 2^31-1 if (value < 0) { throw new H2Exception(ERROR_FLOW_CONTROL_ERROR, "Invalid INITIAL_WINDOW_SIZE: " + (value & 0xFFFFFFFFL)); } - // Update window for all active streams - int delta = value - remoteInitialWindowSize; remoteInitialWindowSize = value; - for (H2Exchange exchange : activeStreams.values()) { - exchange.adjustSendWindow(delta); - } + muxer.onSettingsReceived(remoteMaxConcurrentStreams, value, remoteMaxFrameSize); break; case SETTINGS_MAX_FRAME_SIZE: if (value < MIN_MAX_FRAME_SIZE || value > MAX_MAX_FRAME_SIZE) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, - "Invalid MAX_FRAME_SIZE: " + value); + throw new H2Exception(ERROR_PROTOCOL_ERROR, "Invalid MAX_FRAME_SIZE: " + value); } remoteMaxFrameSize = value; + muxer.onSettingsReceived(remoteMaxConcurrentStreams, remoteInitialWindowSize, value); break; case SETTINGS_MAX_HEADER_LIST_SIZE: - // Server's limit for header list size we can send remoteMaxHeaderListSize = value; break; default: - // Unknown settings are ignored per spec break; } } } + // ==================== Exchange Creation ==================== + @Override public HttpExchange newExchange(HttpRequest request) throws IOException { if (state != State.CONNECTED) { throw new IOException("Connection is not in CONNECTED state: " + state); } - if (goawayReceived) { - // Check if new stream ID would exceed the last allowed stream - int nextId = streamWriter.getNextStreamId(); - if (nextId > goawayLastStreamId) { - throw new IOException("Connection received GOAWAY with lastStreamId=" + - goawayLastStreamId + ", cannot create stream " + nextId); - } - } - - // Fast-fail check (encoder will recheck under serialization) - if (activeStreamCount.get() >= remoteMaxConcurrentStreams) { - throw new IOException("Connection at max concurrent streams: " + activeStreamCount.get()); - } - - // Pre-create the exchange (stream ID will be set by encoder) - H2Exchange exchange = new H2Exchange(this, request, readTimeout, writeTimeout); + // Update last activity time when creating a new exchange + lastActivityTimeNanos = System.nanoTime(); - // Determine if request has a body - boolean hasBody = request.body() != null && request.body().contentLength() != 0; - boolean endStream = !hasBody; + H2Exchange exchange = muxer.newExchange(request, readTimeoutMs, writeTimeoutMs); - CompletableFuture streamIdFuture = new CompletableFuture<>(); - CompletableFuture writeComplete = new CompletableFuture<>(); - - // Submit to writer thread (blocks if queue is full, provides backpressure) - var encodeHeaders = new H2StreamWriter.WorkItem.EncodeHeaders( - request, - exchange, - endStream, - streamIdFuture, - writeComplete); + try { + boolean hasBody = request.body() != null && request.body().contentLength() != 0; + boolean endStream = !hasBody; - if (!streamWriter.submitWork(encodeHeaders, writeTimeout.toMillis())) { - throw new IOException("Write queue full - connection overloaded (timeout after " + writeTimeout + ")"); - } + CompletableFuture streamIdFuture = new CompletableFuture<>(); + CompletableFuture writeComplete = new CompletableFuture<>(); - // Wait for encoding to complete (stream ID assigned, queued to writer) - int streamId; - try { - streamId = streamIdFuture.join(); - } catch (Exception e) { - Throwable cause = e.getCause(); - if (cause instanceof IOException ioe) { - throw ioe; + if (!muxer.submitHeaders(request, + exchange, + endStream, + streamIdFuture, + writeComplete, + writeTimeoutMs)) { + muxer.releaseStreamSlot(); + throw new IOException( + "Write queue full - connection overloaded (timeout after " + writeTimeoutMs + "ms)"); } - throw new IOException("Encoding failed", cause != null ? cause : e); - } - // Wait for write to complete - try { - writeComplete.join(); - } catch (Exception e) { - // Write failed - cleanup and rethrow - activeStreams.remove(streamId); - activeStreamCount.decrementAndGet(); - Throwable cause = e.getCause(); - if (cause instanceof IOException ioe) { - throw ioe; - } - throw new IOException("Failed to write headers", cause != null ? cause : e); - } + int streamId; + try { + streamId = streamIdFuture.join(); + } catch (Exception e) { + muxer.releaseStreamSlot(); + Throwable cause = e.getCause(); + if (cause instanceof IOException ioe) { + throw ioe; + } + throw new IOException("Encoding failed", cause != null ? cause : e); + } - return exchange; - } + try { + writeComplete.join(); + } catch (Exception e) { + muxer.releaseStream(streamId); + Throwable cause = e.getCause(); + if (cause instanceof IOException ioe) { + throw ioe; + } + throw new IOException("Failed to write headers", cause != null ? cause : e); + } - /** - * Unregister a stream when it completes. - */ - @Override - public void unregisterStream(int streamId) { - if (activeStreams.remove(streamId) != null) { - activeStreamCount.decrementAndGet(); - } - } + IOException writeErr = muxer.getWriteError(); + if (writeErr != null) { + muxer.releaseStream(streamId); + throw writeErr; + } - // ==================== StreamManager interface implementation ==================== + return exchange; - @Override - public boolean tryReserveStream() { - while (true) { - int current = activeStreamCount.get(); - if (current >= remoteMaxConcurrentStreams) { - return false; - } - if (activeStreamCount.compareAndSet(current, current + 1)) { - return true; + } catch (IOException e) { + int streamId = exchange.getStreamId(); + if (streamId > 0) { + muxer.releaseStream(streamId); } + throw e; } } - @Override - public void releaseStreamSlot() { - activeStreamCount.decrementAndGet(); - } - - @Override - public void registerStream(int streamId, H2Exchange exchange) { - activeStreams.put(streamId, exchange); - } + // ==================== Connection State ==================== - @Override - public void setLastStreamId(int streamId) { - this.lastStreamId = streamId; - } - - @Override - public boolean isAcceptingStreams() { - return state == State.CONNECTED && !goawayReceived; - } - - @Override - public int getRemoteMaxHeaderListSize() { - return remoteMaxHeaderListSize; + public int getActiveStreamCount() { + return muxer.getActiveStreamCount(); } - // ==================== End StreamManager interface ==================== - /** - * Queue a DATA frame for writing via the encoder/writer thread. + * Check if this connection can accept more streams. * - *

      Flow control must already be checked by the caller. This method - * queues the write and blocks until it completes. - * - * @param streamId the stream ID - * @param data the data buffer - * @param offset offset into the buffer - * @param length number of bytes to write - * @param flags frame flags (e.g., FLAG_END_STREAM) - * @throws IOException if the write fails + *

      This is the primary check used in the connection acquisition hot path. It combines active state, write error, + * and muxer capacity checks to minimize redundant checks. */ - void queueData(int streamId, byte[] data, int offset, int length, int flags) throws IOException { - CompletableFuture completion = new CompletableFuture<>(); - if (!streamWriter.submitWork( - new H2StreamWriter.WorkItem.WriteData(streamId, data, offset, length, flags, completion), - writeTimeout.toMillis())) { - throw new IOException("Write queue full - connection overloaded"); - } - - try { - completion.join(); - } catch (Exception e) { - Throwable cause = e.getCause(); - if (cause instanceof IOException ioe) { - throw ioe; - } - throw new IOException("Failed to write data", cause != null ? cause : e); + public boolean canAcceptMoreStreams() { + // Fast check: if not active, don't bother with other checks + if (!active) { + return false; + } else if (muxer.getWriteError() != null) { + // found write errors (updated by writer thread on failure) + return false; + } else { + // Check muxer capacity + return muxer.canAcceptMoreStreams(); } } /** - * Queue trailer HEADERS frame for writing via the encoder/writer thread. - * - *

      Trailers are sent as a HEADERS frame with END_STREAM flag after all DATA frames. - * Unlike request headers, trailers use an existing stream ID and must not contain - * pseudo-headers. - * - * @param streamId the stream ID - * @param trailers the trailer headers to send - * @throws IOException if the write fails + * Get the active stream count if this connection can accept more streams, or -1 if not. + * Combines the availability check with getting the count to avoid redundant atomic reads + * in the connection acquisition hot path. */ - void queueTrailers(int streamId, HttpHeaders trailers) throws IOException { - CompletableFuture completion = new CompletableFuture<>(); - if (!streamWriter.submitWork( - new H2StreamWriter.WorkItem.WriteTrailers(streamId, trailers, completion), - writeTimeout.toMillis())) { - throw new IOException("Write queue full - connection overloaded"); - } - - try { - completion.join(); - } catch (Exception e) { - Throwable cause = e.getCause(); - if (cause instanceof IOException ioe) { - throw ioe; - } - throw new IOException("Failed to write trailers", cause != null ? cause : e); + public int getActiveStreamCountIfAccepting() { + if (!active) { + return -1; + } else if (muxer.getWriteError() != null) { + return -1; } + return muxer.getActiveStreamCountIfAccepting(); } /** - * Get the current number of active streams. - */ - public int getActiveStreamCount() { - return activeStreamCount.get(); - } - - /** - * Check if connection can accept more streams. + * Get the time in nanoseconds since the last activity on this connection. + * + *

      If there are active streams, returns 0 (not idle). + * Otherwise, returns the time since the last frame was received or exchange was created. + * + * @return idle time in nanoseconds, or 0 if there are active streams */ - public boolean canAcceptMoreStreams() { - return isActive() && activeStreamCount.get() < remoteMaxConcurrentStreams; + public long getIdleTimeNanos() { + if (getActiveStreamCount() > 0) { + return 0; // Not idle if there are active streams + } + return System.nanoTime() - lastActivityTimeNanos; } @Override @@ -738,23 +590,27 @@ public HttpVersion httpVersion() { @Override public boolean isActive() { - return active; + return active && muxer.getWriteError() == null; } @Override public boolean validateForReuse() { + // Fast path: 'active' is set to false by reader thread on socket issues, + // so if it's false, no need for expensive socket checks. if (!active) { return false; } - // Check socket state - if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) { - LOGGER.debug("Connection to {} is closed or half-closed", route); + // Check for write errors + IOException writeErr = muxer.getWriteError(); + if (writeErr != null) { + LOGGER.debug("Connection to {} has write error", route); active = false; state = State.CLOSED; return false; } + // Socket checks skipped here - reader thread sets active=false on socket issues. return true; } @@ -780,9 +636,6 @@ public String negotiatedProtocol() { return "h2"; } - // Timeout for graceful shutdown - allows pending writes to complete - private static final int GRACEFUL_SHUTDOWN_MS = 1000; - @Override public void close() throws IOException { if (state == State.CLOSED) { @@ -790,29 +643,26 @@ public void close() throws IOException { } active = false; - - // Handle both CONNECTED and SHUTTING_DOWN states State previousState = state; state = State.SHUTTING_DOWN; - // Send GOAWAY before closing encoder if (previousState == State.CONNECTED) { - streamWriter.submitControlFrame( - new H2StreamWriter.WorkItem.WriteGoaway(lastStreamId, ERROR_NO_ERROR, null)); + try { + muxer.queueControlFrame(0, + H2Muxer.ControlFrameType.GOAWAY, + new Object[] {muxer.getLastAllocatedStreamId(), ERROR_NO_ERROR, null}, + 100); // Short timeout for shutdown + } catch (IOException ignored) {} } - // Close encoder (gracefully drains pending requests, sends GOAWAY, then stops) - if (streamWriter != null) { - streamWriter.close(); + if (muxer != null) { + muxer.close(); } + muxer.closeExchanges(Duration.ofMillis(GRACEFUL_SHUTDOWN_MS)); state = State.CLOSED; - - // Close socket - this will unblock the reader thread's blocking read. - // Thread.interrupt() doesn't work for socket I/O. socket.close(); - // Wait briefly for reader thread to notice the close if (readerThread != null) { try { readerThread.join(100); @@ -822,53 +672,31 @@ public void close() throws IOException { } } - /** - * Decode HPACK header block. - * - *

      IMPORTANT: This method MUST be called only from the reader thread, - * in frame arrival order. HPACK uses a connection-global dynamic table that - * is updated during decoding. If headers are decoded out of wire order (e.g., - * by different stream threads racing), the dynamic table state becomes corrupted - * and subsequent header blocks will decode incorrectly. - * - *

      Per RFC 9113 Section 4.3, HPACK decoding errors MUST be treated as - * connection errors of type COMPRESSION_ERROR. - * - * @param headerBlock the encoded header block - * @return decoded header fields - * @throws IOException if decoding fails - * @throws H2Exception if header list size exceeds limit or HPACK decoding fails - */ - List decodeHeaders(byte[] headerBlock) throws IOException { - // Check encoded size first (quick rejection) - int maxHeaderListSize = DEFAULT_MAX_HEADER_LIST_SIZE; + // ==================== Helper Methods ==================== + + // Called only from reader thread - no synchronization needed + List decodeHeaders(byte[] headerBlock) throws IOException { + int maxHeaderListSize = H2Constants.DEFAULT_MAX_HEADER_LIST_SIZE; if (headerBlock.length > maxHeaderListSize) { throw new H2Exception(ERROR_ENHANCE_YOUR_CALM, "Header block size " + headerBlock.length + " exceeds limit " + maxHeaderListSize); } - List headers; - // HPACK decoder is stateful (dynamic table), must be synchronized - synchronized (hpackDecoder) { - try { - headers = hpackDecoder.decodeBlock(headerBlock, 0, headerBlock.length); - } catch (IOException e) { - // RFC 9113 Section 4.3: HPACK decoding errors are COMPRESSION_ERROR - active = false; - LOGGER.debug("HPACK decoding failed for {}: {}", route, e.getMessage()); - throw new H2Exception(ERROR_COMPRESSION_ERROR, "HPACK decoding failed: " + e.getMessage()); - } catch (IndexOutOfBoundsException e) { - // Dynamic table index out of range - HPACK state mismatch - // This is a fatal connection error per RFC 9113 Section 4.3 - active = false; - LOGGER.debug("HPACK dynamic table mismatch for {}: {}", route, e.getMessage()); - throw new H2Exception(ERROR_COMPRESSION_ERROR, "HPACK state mismatch: " + e.getMessage()); - } + List headers; + try { + headers = hpackDecoder.decodeBlock(headerBlock, 0, headerBlock.length); + } catch (IOException e) { + active = false; + LOGGER.debug("HPACK decoding failed for {}: {}", route, e.getMessage()); + throw new H2Exception(ERROR_COMPRESSION_ERROR, "HPACK decoding failed: " + e.getMessage()); + } catch (IndexOutOfBoundsException e) { + active = false; + LOGGER.debug("HPACK dynamic table mismatch for {}: {}", route, e.getMessage()); + throw new H2Exception(ERROR_COMPRESSION_ERROR, "HPACK state mismatch: " + e.getMessage()); } - // Check decoded size (name + value + 32 bytes overhead per RFC 7541) int decodedSize = 0; - for (HpackDecoder.HeaderField field : headers) { + for (HeaderField field : headers) { decodedSize += field.name().length() + field.value().length() + 32; if (decodedSize > maxHeaderListSize) { throw new H2Exception(ERROR_ENHANCE_YOUR_CALM, @@ -879,114 +707,19 @@ List decodeHeaders(byte[] headerBlock) throws IOExcept return headers; } - /** - * Get the remote initial window size. - */ - int getRemoteInitialWindowSize() { - return remoteInitialWindowSize; - } - - /** - * Get the remote max frame size. - */ - int getRemoteMaxFrameSize() { - return remoteMaxFrameSize; - } - - /** - * Update connection-level send window. - * - * @param delta window size change (positive for WINDOW_UPDATE) - */ - void updateConnectionSendWindow(int delta) { - connectionSendWindow.addAndGet(delta); - } - - /** - * Get current connection send window. - */ - int getConnectionSendWindow() { - return connectionSendWindow.get(); - } - - /** - * Consume bytes from connection send window. - * - * @param bytes number of bytes to consume - */ - void consumeConnectionSendWindow(int bytes) { - connectionSendWindow.addAndGet(-bytes); - } - - /** - * Queue a stream-level WINDOW_UPDATE frame. - * - * @param streamId the stream ID - * @param increment the window size increment - * @throws IOException if the write queue is full - */ - void queueWindowUpdate(int streamId, int increment) throws IOException { - if (!streamWriter.submitControlFrame( - new H2StreamWriter.WorkItem.WriteWindowUpdate(streamId, increment))) { - throw new IOException("Write queue full - cannot send WINDOW_UPDATE"); - } - } - - /** - * Queue a RST_STREAM frame (fire-and-forget, doesn't wait for completion). - * - * @param streamId the stream ID - * @param errorCode the error code - * @throws IOException if the write queue is full - */ - void queueRst(int streamId, int errorCode) throws IOException { - // Fire-and-forget - no need to wait for completion - if (!streamWriter.submitControlFrame( - new H2StreamWriter.WorkItem.WriteRst(streamId, errorCode, new CompletableFuture<>()))) { - throw new IOException("Write queue full - cannot send RST_STREAM"); - } - } - - /** - * Consume bytes from the connection receive window. - * - *

      In HTTP/2, data received counts against both the stream window AND - * the connection window. This method tracks connection-level consumption - * and queues WINDOW_UPDATE when the window gets low. - * - * @param bytes number of bytes received - * @throws IOException if queue is full after timeout or thread is interrupted - */ + // Called only from reader thread - no synchronization needed void consumeConnectionRecvWindow(int bytes) throws IOException { - int newWindow; - int increment = 0; - synchronized (this) { - connectionRecvWindow -= bytes; - newWindow = connectionRecvWindow; - - // Send WINDOW_UPDATE when window gets below half - if (newWindow < DEFAULT_INITIAL_WINDOW_SIZE / 2) { - increment = DEFAULT_INITIAL_WINDOW_SIZE - newWindow; - connectionRecvWindow += increment; - } - } - - if (increment > 0) { - // Queue connection-level WINDOW_UPDATE - if (!streamWriter.submitWork( - new H2StreamWriter.WorkItem.WriteWindowUpdate(0, increment), - writeTimeout.toMillis())) { - throw new IOException("Write queue full - cannot send connection WINDOW_UPDATE"); - } + connectionRecvWindow -= bytes; + if (connectionRecvWindow < DEFAULT_INITIAL_WINDOW_SIZE / 2) { + int increment = DEFAULT_INITIAL_WINDOW_SIZE - connectionRecvWindow; + connectionRecvWindow += increment; + muxer.queueControlFrame(0, + H2Muxer.ControlFrameType.WINDOW_UPDATE, + increment, + writeTimeoutMs); } } - /** - * Handle GOAWAY frame from server. - * - *

      Per RFC 9113 Section 6.8, streams with IDs greater than the last stream ID - * that was processed by the server should be considered refused and retried. - */ void handleGoaway(int lastStreamId, int errorCode) { goawayReceived = true; goawayLastStreamId = lastStreamId; @@ -997,21 +730,6 @@ void handleGoaway(int lastStreamId, int errorCode) { } state = State.SHUTTING_DOWN; - - // RFC 9113 Section 6.8: Signal streams with ID > lastStreamId that they were refused - // These streams were initiated but not processed by the server - H2Exception refusedError = new H2Exception(errorCode, - "Stream affected by GOAWAY (lastStreamId=" + lastStreamId + - ", error=" + H2Constants.errorCodeName(errorCode) + ")"); - for (var entry : activeStreams.entrySet()) { - int streamId = entry.getKey(); - if (streamId > lastStreamId) { - H2Exchange exchange = entry.getValue(); - exchange.signalConnectionClosed(refusedError); - } - } - - // The encoder will handle failing queued requests when it shuts down - // Streams > lastStreamId that are already queued will fail when processed + muxer.onGoaway(lastStreamId, errorCode); } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java index 7dfa21f0f..e35199930 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java @@ -14,6 +14,11 @@ final class H2Constants { private H2Constants() {} + static final int WRITER_QUEUE_CAPACITY = 2048; + + // Our limit for received header list size (not from server SETTINGS) + static final int DEFAULT_MAX_HEADER_LIST_SIZE = 8192; + // Connection preface - client must send this first (RFC 9113 Section 3.4) static final byte[] CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.US_ASCII); @@ -51,11 +56,9 @@ private H2Constants() {} // Default settings values static final int DEFAULT_HEADER_TABLE_SIZE = 4096; - static final int DEFAULT_ENABLE_PUSH = 1; static final int DEFAULT_MAX_CONCURRENT_STREAMS = Integer.MAX_VALUE; static final int DEFAULT_INITIAL_WINDOW_SIZE = 65535; static final int DEFAULT_MAX_FRAME_SIZE = 16384; - static final int DEFAULT_MAX_HEADER_LIST_SIZE = Integer.MAX_VALUE; // Frame size limits static final int MIN_MAX_FRAME_SIZE = 16384; // 2^14 diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java index 0e4a6c59f..175ebf909 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java @@ -10,13 +10,14 @@ /** * Input stream for reading response body from DATA frames. + * + *

      This implementation reads directly from the exchange's buffer, + * which is filled by the connection's reader thread. This avoids + * per-frame allocations and reduces copying. */ final class H2DataInputStream extends InputStream { private final H2Exchange exchange; - private StreamEvent.DataChunk currentChunk; - private int chunkPos; private boolean closed = false; - private boolean eof = false; H2DataInputStream(H2Exchange exchange) { this.exchange = exchange; @@ -24,59 +25,31 @@ final class H2DataInputStream extends InputStream { @Override public int read() throws IOException { - if (closed || eof) { - return -1; - } else if ((currentChunk == null || chunkPos >= currentChunk.length()) && !loadNextChunk()) { + if (closed) { return -1; } - - return currentChunk.data()[currentChunk.offset() + chunkPos++] & 0xFF; + byte[] buf = new byte[1]; + int n = exchange.readFromBuffer(buf, 0, 1); + return n == 1 ? (buf[0] & 0xFF) : -1; } @Override public int read(byte[] b, int off, int len) throws IOException { - if (closed || eof) { + if (closed) { return -1; } else if (len == 0) { return 0; } - int totalRead = 0; - while (len > 0) { - if (currentChunk == null || chunkPos >= currentChunk.length()) { - if (!loadNextChunk()) { - return totalRead > 0 ? totalRead : -1; - } - } - - int available = currentChunk.length() - chunkPos; - int toCopy = Math.min(available, len); - System.arraycopy(currentChunk.data(), currentChunk.offset() + chunkPos, b, off, toCopy); - chunkPos += toCopy; - off += toCopy; - len -= toCopy; - totalRead += toCopy; - } - - return totalRead; - } - - private boolean loadNextChunk() throws IOException { - currentChunk = exchange.readDataChunk(); - chunkPos = 0; - if (currentChunk.isEnd()) { - eof = true; - return false; - } - return true; + return exchange.readFromBuffer(b, off, len); } @Override public int available() { - if (currentChunk != null && !currentChunk.isEnd()) { - return currentChunk.length() - chunkPos; + if (closed) { + return 0; } - return 0; + return exchange.availableInBuffer(); } @Override diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index 753566984..b8794126d 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -17,12 +17,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.SocketTimeoutException; -import java.time.Duration; -import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -35,14 +32,14 @@ import software.amazon.smithy.java.http.client.DelegatedClosingInputStream; import software.amazon.smithy.java.http.client.DelegatedClosingOutputStream; import software.amazon.smithy.java.http.client.HttpExchange; -import software.amazon.smithy.java.http.client.h2.hpack.HpackDecoder; +import software.amazon.smithy.java.http.client.h2.hpack.HeaderField; /** * HTTP/2 exchange implementation for a single stream with multiplexing support. * *

      This class manages the lifecycle of a single HTTP/2 stream (request/response pair). - * Events (headers, data, errors) are received from the connection's reader thread via a - * single {@link StreamEvent} queue. + * Response data is received from the connection's reader thread directly into a buffer, + * avoiding per-frame allocations. Headers and errors are signaled via condition variables. * *

      Stream Lifecycle

      *
        @@ -52,6 +49,11 @@ *
      1. {@link #responseBody()} returns input stream for response DATA frames
      2. *
      3. {@link #close()} sends RST_STREAM if needed and unregisters stream
      4. *
      + * + *

      Zero-Allocation Read Path

      + *

      The reader thread writes DATA frame payloads directly into {@code dataBuffer}. + * The user thread reads directly from this buffer. Flow control ensures the buffer + * never overflows: we only send WINDOW_UPDATE after user reads consume data. */ public final class H2Exchange implements HttpExchange { @@ -66,6 +68,16 @@ enum StreamState { CLOSED // Both directions closed } + /** + * Read-side state machine for response processing. + */ + enum ReadState { + WAITING_HEADERS, // Initial state, waiting for response HEADERS + READING_DATA, // Got headers, now streaming DATA + DONE, // END_STREAM received + ERROR // Error occurred + } + // Request pseudo-headers (only allowed in requests, not responses) private static final Set REQUEST_PSEUDO_HEADERS = Set.of( ":method", @@ -76,14 +88,30 @@ enum StreamState { // Shared empty array to avoid allocation private static final byte[] EMPTY_DATA = new byte[0]; - private final H2Connection connection; + private final H2Muxer muxer; private final HttpRequest request; private volatile int streamId; - // Unified event queue - all stream events flow through here - // Producer: connection's reader thread - // Consumer: exchange methods (readResponseHeaders, readDataChunk) - private final BlockingQueue eventQueue = new LinkedBlockingQueue<>(); + // Pending headers from reader thread (protected by dataLock) + private List pendingHeaders; + private boolean pendingHeadersEndStream; + + // === Data buffer for zero-allocation read path === + // Lazily allocated when first DATA frame arrives. Size = initial flow control window. + // Flow control ensures server can't send more than this before we send WINDOW_UPDATE. + private byte[] dataBuffer; + + // Buffer positions - protected by dataLock + // writePos: where reader thread writes next (reader-thread owned, but read by user under lock) + // readPos: where user reads next (user-thread owned) + private int writePos = 0; + private int readPos = 0; + + // Read-side state machine and synchronization + private final ReentrantLock dataLock = new ReentrantLock(); + private final Condition dataAvailable = dataLock.newCondition(); + private volatile ReadState readState = ReadState.WAITING_HEADERS; + private volatile IOException readError; // Stream state machine per RFC 9113 Section 5.1 private volatile StreamState streamState = StreamState.IDLE; @@ -98,9 +126,6 @@ enum StreamState { private volatile boolean responseHeadersReceived = false; private volatile boolean endStreamReceived = false; - // Informational responses (1xx) per RFC 9113 Section 8.1.1 - private final List informationalResponses = new ArrayList<>(); - // Trailer headers per RFC 9113 Section 8.1 private volatile HttpHeaders trailerHeaders; @@ -122,33 +147,38 @@ enum StreamState { // Auto-close tracking: exchange closes when both streams are closed (count reaches 2) private final AtomicInteger closedStreamCount = new AtomicInteger(0); - // Flow control with signaling - // streamSendWindow is protected by flowControlLock (accessed by writer and reader threads) - // streamRecvWindow is only accessed by application thread in readDataChunk() (single-threaded) - private final ReentrantLock flowControlLock = new ReentrantLock(); - private final Condition windowAvailable = flowControlLock.newCondition(); - private int streamSendWindow; + // Flow control + // sendWindow: Semaphore-based, VT blocks naturally when exhausted (no lock needed) + // streamRecvWindow: only accessed by application thread in readDataChunk() (single-threaded) + private final FlowControlWindow sendWindow; private int streamRecvWindow; + // === OUTBOUND PATH (VT → Writer) === + // Pending writes queued by VT, drained by writer thread + // ConcurrentLinkedQueue is lock-free and safe for concurrent producer/consumer access + final ConcurrentLinkedQueue pendingWrites = new ConcurrentLinkedQueue<>(); + // Flag to prevent duplicate additions to connection's work queue + volatile boolean inWorkQueue; + /** * Create a new HTTP/2 exchange without a stream ID. * *

      The stream ID will be assigned later via {@link #setStreamId} when - * the connection allocates it under lock. This allows exchange construction - * to happen outside the critical section. + * the muxer allocates it. This allows exchange construction to happen + * outside the critical section. * - * @param connection the HTTP/2 connection + * @param muxer the muxer managing this stream * @param request the HTTP request - * @param readTimeout timeout for waiting on response data - * @param writeTimeout timeout for waiting on flow control window + * @param readTimeoutMs timeout in milliseconds for waiting on response data + * @param writeTimeoutMs timeout in milliseconds for waiting on flow control window */ - H2Exchange(H2Connection connection, HttpRequest request, Duration readTimeout, Duration writeTimeout) { - this.connection = connection; + H2Exchange(H2Muxer muxer, HttpRequest request, long readTimeoutMs, long writeTimeoutMs) { + this.muxer = muxer; this.request = request; this.streamId = -1; // Will be set later - this.readTimeoutMs = readTimeout.toMillis(); - this.writeTimeoutMs = writeTimeout.toMillis(); - this.streamSendWindow = connection.getRemoteInitialWindowSize(); + this.readTimeoutMs = readTimeoutMs; + this.writeTimeoutMs = writeTimeoutMs; + this.sendWindow = new FlowControlWindow(muxer.getRemoteInitialWindowSize()); this.streamRecvWindow = DEFAULT_INITIAL_WINDOW_SIZE; } @@ -159,6 +189,31 @@ void setStreamId(int streamId) { this.streamId = streamId; } + /** + * Get the stream ID. + */ + int getStreamId() { + return streamId; + } + + /** + * Get the muxer for this exchange. + */ + H2Muxer getMuxer() { + return muxer; + } + + /** + * Return a buffer to the muxer's pool. + * + *

      Called by the writer thread after consuming a PendingWrite. + * + * @param buffer the buffer to return + */ + void returnBuffer(byte[] buffer) { + muxer.returnBuffer(buffer); + } + /** * Called by connection after headers are encoded but before they're written. * @@ -177,70 +232,181 @@ void onHeadersEncoded(boolean endStream) { } /** - * Called by connection's reader thread to deliver an event. + * Called by connection's reader thread to deliver response headers. * - *

      All stream events (headers, data, errors) flow through this single method. - * The reader thread is the only producer; exchange methods are the only consumers. + *

      Headers are decoded by the reader thread to ensure HPACK state consistency. + * This method signals the user thread that headers are available. + * + * @param fields the decoded header fields + * @param endStream whether END_STREAM flag was set */ - void enqueueEvent(StreamEvent event) { - eventQueue.add(event); + void deliverHeaders(List fields, boolean endStream) { + dataLock.lock(); + try { + // Process headers and update state (will be called from reader thread) + // The actual header processing happens here rather than in user thread + // to avoid needing a separate queue for headers + pendingHeaders = fields; + pendingHeadersEndStream = endStream; + dataAvailable.signalAll(); + } finally { + dataLock.unlock(); + } } /** * Called by connection when it's closing. * - *

      Note: We set endStreamReceived but NOT streamState here. Setting streamState - * would race with pending events in the queue - handleHeadersEvent checks - * streamState and would throw a protocol error for legitimate pending events. - * The error event will be processed after pending events. + *

      Signals the user thread that the connection has closed with an error. */ void signalConnectionClosed(Throwable error) { - this.endStreamReceived = true; - IOException cause = (error instanceof IOException ioe) ? ioe : new IOException("Connection closed", error); - eventQueue.add(new StreamEvent.ConnectionError(cause)); + dataLock.lock(); + try { + this.endStreamReceived = true; + this.readError = (error instanceof IOException ioe) ? ioe : new IOException("Connection closed", error); + this.readState = ReadState.ERROR; + dataAvailable.signalAll(); + } finally { + dataLock.unlock(); + } } /** * Called by reader thread when a per-stream error occurs (e.g., RST_STREAM). * - *

      This allows readResponseHeaders() and readDataChunk() to fail fast with - * a meaningful error instead of timing out. - * - *

      Note: We set endStreamReceived but NOT streamState to avoid racing with - * pending events in the queue. + *

      This allows read operations to fail fast with a meaningful error + * instead of timing out. */ void signalStreamError(H2Exception error) { - this.endStreamReceived = true; - eventQueue.add(new StreamEvent.StreamError(error)); + dataLock.lock(); + try { + this.endStreamReceived = true; + this.readError = new IOException("Stream error", error); + this.readState = ReadState.ERROR; + dataAvailable.signalAll(); + } finally { + dataLock.unlock(); + } } /** - * Called by connection when WINDOW_UPDATE is received. + * Get the data buffer for direct writes by the reader thread. + * + *

      Must be called while holding dataLock (via ensureBufferSpace). */ - void signalWindowUpdate() { - flowControlLock.lock(); + byte[] getDataBuffer() { + return dataBuffer; + } + + /** + * Get the current write position for direct writes by the reader thread. + * + *

      Must be called while holding dataLock (via ensureBufferSpace). + */ + int getWritePos() { + return writePos; + } + + /** + * Called by reader thread after writing data to advance the write position. + * + *

      Note: We do NOT call updateStreamStateOnEndStream() here. The stream + * state transition happens when the user finishes reading (returns -1), + * not when data arrives. This avoids race conditions where the stream + * transitions to CLOSED before the user processes pending headers. + * + * @param bytesWritten number of bytes written + * @param endStream whether END_STREAM flag was set + */ + void commitWrite(int bytesWritten, boolean endStream) { + dataLock.lock(); try { - windowAvailable.signalAll(); + writePos += bytesWritten; + if (endStream) { + this.endStreamReceived = true; + this.readState = ReadState.DONE; + // Don't update streamState here - it will be updated when + // the user finishes reading and we return -1 + } + dataAvailable.signalAll(); } finally { - flowControlLock.unlock(); + dataLock.unlock(); } } + // Initial buffer size - small to avoid waste on tiny responses + private static final int INITIAL_BUFFER_SIZE = 1024; + /** - * Called by connection when SETTINGS changes initial window size. + * Called by reader thread to ensure buffer has space, allocating or compacting as needed. + * + *

      Buffer is borrowed from the connection's pool when first needed, and returned + * when the exchange is closed. This reduces GC pressure for repeated requests. + * + * @param requiredSpace the amount of space needed + * @return true if space is available, false if buffer is genuinely full */ - void adjustSendWindow(int delta) { - flowControlLock.lock(); + boolean ensureBufferSpace(int requiredSpace) { + dataLock.lock(); try { - streamSendWindow += delta; - if (streamSendWindow > 0) { - windowAvailable.signalAll(); + // Lazy allocation on first DATA frame - borrow from connection pool + if (dataBuffer == null) { + int size = Math.max(INITIAL_BUFFER_SIZE, requiredSpace); + size = Math.min(size, DEFAULT_INITIAL_WINDOW_SIZE); + dataBuffer = muxer.borrowBuffer(size); + return true; + } + + if (writePos + requiredSpace <= dataBuffer.length) { + // Already have space + return true; } + + // Try compaction first + if (readPos > 0) { + int remaining = writePos - readPos; + if (remaining > 0) { + System.arraycopy(dataBuffer, readPos, dataBuffer, 0, remaining); + } + writePos = remaining; + readPos = 0; + + if (writePos + requiredSpace <= dataBuffer.length) { + return true; + } + } + + // Need to grow buffer - get a larger one from pool + int newSize = dataBuffer.length * 2; + while (newSize < writePos + requiredSpace) { + newSize *= 2; + } + newSize = Math.min(newSize, DEFAULT_INITIAL_WINDOW_SIZE); + + if (writePos + requiredSpace > newSize) { + return false; + } + + byte[] oldBuffer = dataBuffer; + dataBuffer = muxer.borrowBuffer(newSize); + if (writePos > 0) { + System.arraycopy(oldBuffer, 0, dataBuffer, 0, writePos); + } + // Return old buffer to pool + muxer.returnBuffer(oldBuffer); + return true; } finally { - flowControlLock.unlock(); + dataLock.unlock(); } } + /** + * Called by connection when SETTINGS changes initial window size. + */ + void adjustSendWindow(int delta) { + sendWindow.adjust(delta); + } + @Override public HttpRequest request() { return request; @@ -252,7 +418,7 @@ public OutputStream requestBody() { // If no request body is expected, then return a no-op stream. H2DataOutputStream rawOut = endStreamSent ? new H2DataOutputStream(this, 0) - : new H2DataOutputStream(this, connection.getRemoteMaxFrameSize()); + : new H2DataOutputStream(this, muxer.getRemoteMaxFrameSize()); requestOut = new DelegatedClosingOutputStream(rawOut, rw -> { rw.close(); // Send END_STREAM onRequestStreamClosed(); @@ -333,12 +499,30 @@ public void close() { // If response not fully received and stream was started, queue RST_STREAM if (!endStreamReceived && streamId > 0 && streamState != StreamState.CLOSED) { try { - connection.queueRst(streamId, ERROR_CANCEL); + // Short timeout for cleanup + muxer.queueControlFrame(streamId, H2Muxer.ControlFrameType.RST_STREAM, ERROR_CANCEL, 100); } catch (IOException ignored) { // Best-effort cleanup. If queue is full, stream is closing anyway. } - // Signal end to any waiting consumers only if stream didn't end naturally - eventQueue.add(StreamEvent.DataChunk.END); + // Signal end to any waiting consumers + dataLock.lock(); + try { + readState = ReadState.DONE; + dataAvailable.signalAll(); + } finally { + dataLock.unlock(); + } + } + + // Return buffer to connection pool for reuse + dataLock.lock(); + try { + if (dataBuffer != null) { + muxer.returnBuffer(dataBuffer); + dataBuffer = null; + } + } finally { + dataLock.unlock(); } // Mark stream as closed @@ -346,68 +530,82 @@ public void close() { // Unregister from connection (only if stream was registered) if (streamId > 0) { - connection.unregisterStream(streamId); + muxer.releaseStream(streamId); } } /** - * Poll for the next event from the event queue. + * Wait for the next event from the reader thread. * - *

      This is a simple blocking poll with timeout. Error events are handled - * inline by callers to avoid double pattern matching on the hot path. + *

      Used for waiting on headers and errors. Data is read directly from + * the buffer, not via this method. * - * @return the next event (may be an error event) * @throws SocketTimeoutException if read timeout expires - * @throws IOException if interrupted + * @throws IOException if interrupted or error occurred */ - private StreamEvent pollEvent() throws IOException { + private void awaitEvent() throws IOException { + dataLock.lock(); try { - StreamEvent event = eventQueue.poll(readTimeoutMs, TimeUnit.MILLISECONDS); - if (event == null) { - throw new SocketTimeoutException("Read timed out after " + readTimeoutMs + "ms waiting for response"); + // Wait for headers, error, or data (which also signals) + while (pendingHeaders == null && readState != ReadState.ERROR && readState != ReadState.DONE) { + if (!dataAvailable.await(readTimeoutMs, TimeUnit.MILLISECONDS)) { + throw new SocketTimeoutException( + "Read timed out after " + readTimeoutMs + "ms waiting for response"); + } + } + + // Check for error + if (readState == ReadState.ERROR) { + throw readError; } - return event; } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new IOException("Interrupted waiting for stream event", e); + throw new IOException("Interrupted waiting for response", e); + } finally { + dataLock.unlock(); } } /** - * Read and parse response headers from the event queue. + * Read and parse response headers. * *

      Headers are decoded by the connection's reader thread to ensure * HPACK dynamic table consistency across all streams. */ private void readResponseHeaders() throws IOException { while (!responseHeadersReceived) { - switch (pollEvent()) { - case StreamEvent.Headers h -> handleHeadersEvent(h); - case StreamEvent.DataChunk chunk -> throw new H2Exception(ERROR_PROTOCOL_ERROR, - streamId, - "Received DATA before response headers"); - case StreamEvent.StreamError se -> throw new IOException( - "Stream error before response headers", - se.cause()); - case StreamEvent.ConnectionError ce -> throw new IOException( - "Connection error before response headers", - ce.cause()); + awaitEvent(); + + dataLock.lock(); + try { + if (pendingHeaders != null) { + List fields = pendingHeaders; + boolean endStream = pendingHeadersEndStream; + pendingHeaders = null; // Consume the headers + + // Process headers (can throw) + handleHeadersEvent(fields, endStream); + } else if (readState == ReadState.DONE) { + throw new IOException("Stream ended before response headers received"); + } + } finally { + dataLock.unlock(); } } } /** * Handle a headers event during response reading. + * + * @param fields the decoded header fields + * @param isEndStream whether END_STREAM flag was set */ - private void handleHeadersEvent(StreamEvent.Headers event) throws IOException { + private void handleHeadersEvent(List fields, boolean isEndStream) throws IOException { // Validate stream state per RFC 9113 Section 5.1 if (streamState == StreamState.CLOSED) { throw new H2Exception(ERROR_STREAM_CLOSED, streamId, "Received HEADERS on closed stream"); } - List fields = event.fields(); - boolean isEndStream = event.endStream(); - if (!responseHeadersReceived) { // This is either informational (1xx) or final response headers if (fields.isEmpty()) { @@ -427,8 +625,13 @@ private void handleHeadersEvent(StreamEvent.Headers event) throws IOException { if (isEndStream) { endStreamReceived = true; + readState = ReadState.DONE; updateStreamStateOnEndStream(); validateContentLength(); + } else if (responseHeadersReceived && readState != ReadState.DONE) { + // Got final headers, transition to READING_DATA + // (but don't overwrite DONE if DATA+END_STREAM already arrived) + readState = ReadState.READING_DATA; } } @@ -451,13 +654,13 @@ private void updateStreamStateOnEndStream() { * @param fields the decoded header fields * @param isEndStream whether this HEADERS frame has END_STREAM flag */ - private void processResponseHeaders(List fields, boolean isEndStream) throws IOException { + private void processResponseHeaders(List fields, boolean isEndStream) throws IOException { ModifiableHttpHeaders headers = HttpHeaders.ofModifiable(); int parsedStatusCode = -1; boolean seenRegularHeader = false; long contentLength = -1; - for (HpackDecoder.HeaderField field : fields) { + for (HeaderField field : fields) { String name = field.name(); String value = field.value(); @@ -522,17 +725,15 @@ private void processResponseHeaders(List fields, boole throw new IOException("Response missing :status pseudo-header"); } - // Check if this is an informational (1xx) response + // Check if this is an informational (1xx) response - skip and wait for final response if (parsedStatusCode >= 100 && parsedStatusCode < 200) { - // RFC 9113 Section 8.1.1: Informational responses are interim, continue waiting - // 1xx responses MUST NOT have END_STREAM (except 101 which is not allowed in HTTP/2) + // RFC 9113 Section 8.1.1: 1xx responses MUST NOT have END_STREAM if (isEndStream) { throw new H2Exception(ERROR_PROTOCOL_ERROR, streamId, "Informational response (1xx) must not have END_STREAM"); } - informationalResponses.add(new InformationalResponse(parsedStatusCode, headers)); - // Don't mark responseHeadersReceived - wait for final response + // Don't store 1xx responses - just wait for final response return; } @@ -553,9 +754,9 @@ private void processResponseHeaders(List fields, boole * * @param fields the pre-decoded header fields */ - private void processTrailers(List fields) throws IOException { + private void processTrailers(List fields) throws IOException { ModifiableHttpHeaders trailers = HttpHeaders.ofModifiable(); - for (HpackDecoder.HeaderField field : fields) { + for (HeaderField field : fields) { String name = field.name(); // RFC 9113 Section 8.1: Trailers MUST NOT contain pseudo-headers if (name.startsWith(":")) { @@ -586,31 +787,38 @@ private void validateContentLength() throws IOException { * Update stream send window from WINDOW_UPDATE frame. * *

      Called by the connection's reader thread when a stream-level - * WINDOW_UPDATE is received. This is separate from the event queue - * because WINDOW_UPDATE affects the request send path, not response reading. + * WINDOW_UPDATE is received. This releases permits to the FlowControlWindow, + * which will automatically wake any blocked threads. * * @param increment the window size increment * @throws H2Exception if the increment causes overflow */ void updateStreamSendWindow(int increment) throws H2Exception { - flowControlLock.lock(); - try { - streamSendWindow += increment; - // Check for overflow per RFC 9113 (wrap-around to negative) - if (streamSendWindow < 0) { - throw new H2Exception(ERROR_FLOW_CONTROL_ERROR, - streamId, - "Stream send window overflow: " + streamSendWindow); - } - windowAvailable.signalAll(); - } finally { - flowControlLock.unlock(); + // Check for overflow per RFC 9113 before releasing + // Note: with Semaphore, we can't easily detect overflow, so we check available + increment + int currentWindow = sendWindow.available(); + if ((long) currentWindow + increment > Integer.MAX_VALUE) { + throw new H2Exception(ERROR_FLOW_CONTROL_ERROR, + streamId, + "Stream send window overflow"); } + sendWindow.release(increment); } /** * Write DATA frame for request body with flow control. * + *

      Uses the SPSC (single-producer, single-consumer) pattern: + *

        + *
      1. VT acquires flow control (blocks naturally via Semaphore)
      2. + *
      3. VT copies data to pooled buffer and adds to pendingWrites queue
      4. + *
      5. VT signals writer thread via dataWorkQueue
      6. + *
      7. Writer thread drains pendingWrites and writes frames
      8. + *
      + * + *

      This eliminates contention on the shared ArrayBlockingQueue by using + * per-exchange queues for data. + * * @throws SocketTimeoutException if write timeout expires waiting for flow control window */ void writeData(byte[] data, int offset, int length, boolean endStream) throws IOException { @@ -619,39 +827,53 @@ void writeData(byte[] data, int offset, int length, boolean endStream) throws IO boolean hasTrailers = requestTrailers != null; while (length > 0) { - int toSend; + // Determine how much we can send based on frame size limit + int maxFrameSize = muxer.getRemoteMaxFrameSize(); + int toSend = Math.min(length, maxFrameSize); - flowControlLock.lock(); + // Acquire stream-level flow control (blocks naturally via Semaphore) try { - // Wait for flow control window - need BOTH stream AND connection windows to be positive - while (streamSendWindow <= 0 || connection.getConnectionSendWindow() <= 0) { - try { - if (!windowAvailable.await(writeTimeoutMs, TimeUnit.MILLISECONDS)) { - throw new SocketTimeoutException( - "Write timed out after " + writeTimeoutMs + "ms waiting for flow control window"); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IOException("Interrupted waiting for flow control window", e); - } + if (!sendWindow.tryAcquire(toSend, writeTimeoutMs, TimeUnit.MILLISECONDS)) { + throw new SocketTimeoutException( + "Write timed out after " + writeTimeoutMs + "ms waiting for stream flow control window"); } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Interrupted waiting for stream flow control window", e); + } - int available = Math.min(streamSendWindow, connection.getConnectionSendWindow()); - available = Math.min(available, connection.getRemoteMaxFrameSize()); - toSend = Math.min(available, length); - - streamSendWindow -= toSend; - } finally { - flowControlLock.unlock(); + // Acquire connection-level flow control + try { + muxer.acquireConnectionWindow(toSend, writeTimeoutMs); + } catch (InterruptedException e) { + // Release stream permits we acquired + sendWindow.release(toSend); + Thread.currentThread().interrupt(); + throw new IOException("Interrupted waiting for connection flow control window", e); + } catch (SocketTimeoutException e) { + // Release stream permits we acquired + sendWindow.release(toSend); + throw e; } boolean isLastChunk = (toSend == length); // Only set END_STREAM on DATA if this is the last chunk AND no trailers int flags = (endStream && isLastChunk && !hasTrailers) ? FLAG_END_STREAM : 0; - // Queue the write - writer thread handles I/O with batching - connection.queueData(streamId, data, offset, toSend, flags); - connection.consumeConnectionSendWindow(toSend); + // Copy data to pooled buffer (caller may reuse their buffer) + byte[] buf = muxer.borrowBuffer(toSend); + System.arraycopy(data, offset, buf, 0, toSend); + + // Add to pendingWrites queue (lock-free concurrent queue) + PendingWrite pw = new PendingWrite(); + pw.init(buf, 0, toSend, isLastChunk && endStream && !hasTrailers, flags); + pendingWrites.add(pw); + + // Signal writer thread if not already in work queue + if (!inWorkQueue) { + inWorkQueue = true; + muxer.signalDataReady(this); + } offset += toSend; length -= toSend; @@ -660,7 +882,7 @@ void writeData(byte[] data, int offset, int length, boolean endStream) throws IO if (endStream) { if (hasTrailers) { // Send trailers with END_STREAM - connection.queueTrailers(streamId, requestTrailers); + muxer.queueTrailers(streamId, requestTrailers); } endStreamSent = true; // Update stream state @@ -678,9 +900,9 @@ void writeData(byte[] data, int offset, int length, boolean endStream) throws IO void sendEndStream() throws IOException { if (!endStreamSent) { if (requestTrailers != null) { - connection.queueTrailers(streamId, requestTrailers); + muxer.queueTrailers(streamId, requestTrailers); } else { - connection.queueData(streamId, EMPTY_DATA, 0, 0, FLAG_END_STREAM); + muxer.queueData(streamId, EMPTY_DATA, 0, 0, FLAG_END_STREAM); } endStreamSent = true; // Update stream state @@ -693,72 +915,96 @@ void sendEndStream() throws IOException { } /** - * Read next data chunk from response. + * Read data from the response buffer. + * + *

      This is the primary read method for H2DataInputStream. Data is read + * directly from the exchange's buffer, which is filled by the reader thread. * - *

      Data chunks arrive via the unified event queue from the connection's - * reader thread. This method also handles trailers (HEADERS with END_STREAM - * after DATA) and stream-level flow control. + * @param buf the destination buffer + * @param off offset in the destination buffer + * @param len maximum number of bytes to read + * @return number of bytes read, or -1 if end of stream + * @throws IOException if an error occurs */ - StreamEvent.DataChunk readDataChunk() throws IOException { + int readFromBuffer(byte[] buf, int off, int len) throws IOException { // If we haven't received headers yet, read them first if (!responseHeadersReceived) { readResponseHeaders(); } - // If we already know stream has ended, return immediately - if (endStreamReceived) { - return StreamEvent.DataChunk.END; - } - - while (true) { - StreamEvent event = pollEvent(); - - switch (event) { - case StreamEvent.DataChunk chunk -> { - // Track received content length for validation - if (chunk.data() != null && chunk.length() > 0) { - receivedContentLength += chunk.length(); - - // Update stream-level flow control (only if stream is not ending) - // Connection-level flow control is handled by the connection - if (!chunk.endStream()) { - updateStreamRecvWindow(chunk.length()); - } - } - - if (chunk.endStream()) { - endStreamReceived = true; - updateStreamStateOnEndStream(); - validateContentLength(); + int bytesRead = 0; + dataLock.lock(); + try { + // Wait for data, EOF, or error + while (readPos == writePos && readState == ReadState.READING_DATA) { + // Check for pending trailers + if (pendingHeaders != null) { + List fields = pendingHeaders; + boolean endStream = pendingHeadersEndStream; + pendingHeaders = null; + handleHeadersEvent(fields, endStream); + if (readState == ReadState.DONE) { + break; } - - return chunk; } - case StreamEvent.Headers h -> { - // Headers after data = trailers (must have END_STREAM) - if (!h.endStream()) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, - streamId, - "Trailer HEADERS frame must have END_STREAM flag"); - } - if (!h.fields().isEmpty()) { - processTrailers(h.fields()); + try { + if (!dataAvailable.await(readTimeoutMs, TimeUnit.MILLISECONDS)) { + throw new SocketTimeoutException( + "Read timed out after " + readTimeoutMs + "ms waiting for data"); } - endStreamReceived = true; - updateStreamStateOnEndStream(); - validateContentLength(); - return StreamEvent.DataChunk.END; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Interrupted waiting for data", e); } + } - case StreamEvent.StreamError se -> throw new IOException( - "Stream error while reading data", - se.cause()); + // Check for error + if (readState == ReadState.ERROR) { + throw readError; + } - case StreamEvent.ConnectionError ce -> throw new IOException( - "Connection error while reading data", - ce.cause()); + // Check for EOF (no more data and stream is done) + if (readPos == writePos && readState == ReadState.DONE) { + updateStreamStateOnEndStream(); + validateContentLength(); + return -1; } + + // Copy from buffer to user's array + int available = writePos - readPos; + int toCopy = Math.min(available, len); + System.arraycopy(dataBuffer, readPos, buf, off, toCopy); + readPos += toCopy; + bytesRead = toCopy; + + // Track received content length for validation + receivedContentLength += toCopy; + + } finally { + dataLock.unlock(); + } + + // Update stream-level flow control outside the lock + // This sends WINDOW_UPDATE after user reads consume data + if (bytesRead > 0 && readState != ReadState.DONE) { + updateStreamRecvWindow(bytesRead); + } + + return bytesRead; + } + + /** + * Get available bytes in buffer without blocking. + * + * @return number of bytes available for immediate read + */ + int availableInBuffer() { + dataLock.lock(); + try { + return dataBuffer == null ? 0 : writePos - readPos; + } finally { + dataLock.unlock(); } } @@ -774,34 +1020,13 @@ private void updateStreamRecvWindow(int bytesConsumed) throws IOException { if (streamRecvWindow < DEFAULT_INITIAL_WINDOW_SIZE / 2) { int increment = DEFAULT_INITIAL_WINDOW_SIZE - streamRecvWindow; // Queue stream-level WINDOW_UPDATE - writer thread will send it - connection.queueWindowUpdate(streamId, increment); + muxer.queueControlFrame(streamId, H2Muxer.ControlFrameType.WINDOW_UPDATE, increment, writeTimeoutMs); streamRecvWindow += increment; } } - /** - * Get informational (1xx) responses received before the final response. - * - *

      Per RFC 9113 Section 8.1.1, a server may send one or more informational - * responses (status codes 100-199) before the final response. Common examples - * include 100 Continue and 103 Early Hints. - * - * @return list of informational responses (may be empty, never null) - */ - public List informationalResponses() { - return List.copyOf(informationalResponses); - } - @Override public HttpHeaders responseTrailerHeaders() { return trailerHeaders; } - - /** - * Informational response (1xx) received before the final response. - * - * @param statusCode the informational status code (100-199) - * @param headers the headers from this informational response - */ - public record InformationalResponse(int statusCode, HttpHeaders headers) {} } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java index 14c941434..28be8b3ef 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java @@ -583,6 +583,106 @@ private int readFully(byte[] buf, int off, int len) throws IOException { return total; } + /** + * Read payload bytes directly into a provided buffer. + * + *

      This method is used by the reader thread to read DATA frame payloads + * directly into an exchange's buffer, avoiding an intermediate allocation. + * + * @param dest the destination buffer + * @param offset offset in the destination buffer + * @param length number of bytes to read + * @throws IOException if reading fails or EOF is reached before all bytes are read + */ + void readPayloadInto(byte[] dest, int offset, int length) throws IOException { + int read = readFully(dest, offset, length); + if (read < length) { + throw new IOException("Incomplete payload: expected " + length + ", read " + read); + } + } + + /** + * Read a single byte from the input stream. + * + *

      Used for reading pad length in padded DATA frames without allocating. + * + * @return the byte value (0-255) + * @throws IOException if reading fails or EOF is reached + */ + int readByte() throws IOException { + int b = in.read(); + if (b < 0) { + throw new IOException("Unexpected EOF reading byte"); + } + return b; + } + + /** + * Read the frame header only, without reading the payload. + * + *

      This is used for DATA frames where we want to read the payload directly + * into the exchange buffer, avoiding an intermediate allocation. + * + * @return a FrameHeader with type, flags, streamId, and payload length, or null if EOF + * @throws IOException if reading fails or frame is malformed + */ + FrameHeader readFrameHeader() throws IOException { + // Read 9-byte header + int read = readFully(readHeaderBuf, 0, FRAME_HEADER_SIZE); + if (read < FRAME_HEADER_SIZE) { + if (read == 0) { + return null; // EOF + } + throw new IOException("Incomplete frame header: read " + read + " bytes"); + } + + // Parse header + int length = ((readHeaderBuf[0] & 0xFF) << 16) | ((readHeaderBuf[1] & 0xFF) << 8) | (readHeaderBuf[2] & 0xFF); + int type = readHeaderBuf[3] & 0xFF; + int flags = readHeaderBuf[4] & 0xFF; + int streamId = ((readHeaderBuf[5] & 0x7F) << 24) // Mask off reserved bit + | ((readHeaderBuf[6] & 0xFF) << 16) + | ((readHeaderBuf[7] & 0xFF) << 8) + | (readHeaderBuf[8] & 0xFF); + + // Validate frame size + if (length > maxFrameSize) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, "Frame size " + length + " exceeds " + maxFrameSize); + } + + return new FrameHeader(type, flags, streamId, length); + } + + /** + * Skip the specified number of bytes in the input stream. + * + *

      Used to skip past padding bytes in DATA frames. + * + * @param length number of bytes to skip + * @throws IOException if skipping fails + */ + void skipBytes(int length) throws IOException { + int remaining = length; + while (remaining > 0) { + // Use scratch buffer for skipping + int toRead = Math.min(remaining, CONTROL_FRAME_SCRATCH_SIZE); + int read = readFully(controlFrameScratch, 0, toRead); + if (read < toRead) { + throw new IOException("Unexpected EOF while skipping " + length + " bytes"); + } + remaining -= toRead; + } + } + + /** + * Frame header information for deferred payload reading. + */ + record FrameHeader(int type, int flags, int streamId, int payloadLength) { + boolean hasFlag(int flag) { + return (flags & flag) != 0; + } + } + /** * Represents an HTTP/2 frame. * diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java new file mode 100644 index 000000000..4d2037996 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java @@ -0,0 +1,816 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import static software.amazon.smithy.java.http.client.h2.H2Constants.DEFAULT_INITIAL_WINDOW_SIZE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.DEFAULT_MAX_CONCURRENT_STREAMS; +import static software.amazon.smithy.java.http.client.h2.H2Constants.DEFAULT_MAX_FRAME_SIZE; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_ACK; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_DATA; +import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_PING; +import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_AUTHORITY; +import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_METHOD; +import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_PATH; +import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_SCHEME; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.nio.ByteBuffer; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.client.BufferPool; +import software.amazon.smithy.java.http.client.h2.hpack.HpackEncoder; +import software.amazon.smithy.java.io.ByteBufferOutputStream; + +/** + * HTTP/2 stream multiplexer that coordinates concurrent streams over a single connection. + * + *

      This class manages: + *

        + *
      • Stream registry and lifecycle
      • + *
      • Connection and stream flow control
      • + *
      • HPACK encoding and frame writing (via dedicated writer thread)
      • + *
      • Work queue processing with batching
      • + *
      + * + *

      Threading Model

      + *
        + *
      • Reader thread calls {@code on*} methods to deliver inbound frames
      • + *
      • User VTs call {@code newExchange}, queue writes via exchanges
      • + *
      • Writer thread processes queued work: encodes headers, writes frames
      • + *
      + */ +final class H2Muxer implements AutoCloseable { + + /** + * Callback interface for connection-level operations. + */ + interface ConnectionCallback { + boolean isAcceptingStreams(); + + int getRemoteMaxHeaderListSize(); + } + + /** + * Work items processed by the writer thread. + */ + private sealed interface WorkItem { + record EncodeHeaders( + HttpRequest request, + H2Exchange exchange, + boolean endStream, + CompletableFuture streamIdFuture, + CompletableFuture writeComplete) implements WorkItem {} + + record WriteData( + int streamId, + byte[] data, + int offset, + int length, + int flags, + CompletableFuture completion) implements WorkItem {} + + record WriteTrailers( + int streamId, + HttpHeaders trailers, + CompletableFuture completion) implements WorkItem {} + + record WriteRst(int streamId, int errorCode) implements WorkItem {} + + record WriteGoaway(int lastStreamId, int errorCode, String debugData) implements WorkItem {} + + record WriteWindowUpdate(int streamId, int increment) implements WorkItem {} + + record WriteSettingsAck() implements WorkItem {} + + record WritePing(byte[] payload, boolean ack) implements WorkItem {} + + record Shutdown() implements WorkItem {} + + record CheckDataQueue() implements WorkItem {} + } + + enum ControlFrameType { + RST_STREAM, + WINDOW_UPDATE, + SETTINGS_ACK, + PING, + GOAWAY + } + + // Headers that must not be sent over HTTP/2 (connection-specific) + private static final Set CONNECTION_HEADERS = Set.of( + "connection", + "keep-alive", + "proxy-connection", + "transfer-encoding", + "upgrade", + "host"); + + // Headers that should not be indexed in HPACK (contain sensitive data) + private static final Set SENSITIVE_HEADERS = Set.of( + "authorization", + "cookie", + "proxy-authorization", + "set-cookie"); + + // Singleton wake-up signal + private static final WorkItem.CheckDataQueue CHECK_DATA_QUEUE = new WorkItem.CheckDataQueue(); + + // === STREAM REGISTRY === + private final StreamRegistry streams = new StreamRegistry(); + private final AtomicInteger activeStreamCount = new AtomicInteger(0); + private final AtomicInteger nextStreamId = new AtomicInteger(1); + private volatile int lastAllocatedStreamId = 0; + + // === SETTINGS FROM PEER === + private volatile int remoteMaxConcurrentStreams = DEFAULT_MAX_CONCURRENT_STREAMS; + private volatile int remoteInitialWindowSize = DEFAULT_INITIAL_WINDOW_SIZE; + private volatile int remoteMaxFrameSize = DEFAULT_MAX_FRAME_SIZE; + + // === CONNECTION FLOW CONTROL === + private final FlowControlWindow connectionSendWindow = new FlowControlWindow(DEFAULT_INITIAL_WINDOW_SIZE); + + // === STATE === + private volatile boolean accepting = true; + private volatile boolean running = true; + private volatile boolean goawayReceived = false; + private volatile int goawayLastStreamId = Integer.MAX_VALUE; + private volatile IOException writeError; + + // === DEPENDENCIES === + private final ConnectionCallback connectionCallback; + private final H2FrameCodec frameCodec; + private final BufferPool bufferPool; + + // === WORK QUEUES === + private final BlockingQueue workQueue; + private final ConcurrentLinkedQueue dataWorkQueue = new ConcurrentLinkedQueue<>(); + private volatile boolean dataWorkPending; + + // === HPACK ENCODER (only accessed by writer thread) === + private final HpackEncoder hpackEncoder; + private final ByteBufferOutputStream headerEncodeBuffer; + private volatile int pendingTableSizeUpdate = -1; + + // === WRITER THREAD === + private final Thread workerThread; + + /** + * Create a new multiplexer. + * + * @param connectionCallback callback for connection-level state + * @param frameCodec the frame codec for writing + * @param initialTableSize initial HPACK table size + * @param threadName name for the writer thread + */ + H2Muxer(ConnectionCallback connectionCallback, H2FrameCodec frameCodec, int initialTableSize, String threadName) { + this.connectionCallback = connectionCallback; + this.frameCodec = frameCodec; + this.bufferPool = new BufferPool(32, DEFAULT_INITIAL_WINDOW_SIZE, DEFAULT_INITIAL_WINDOW_SIZE, 1024); + this.workQueue = new ArrayBlockingQueue<>(H2Constants.WRITER_QUEUE_CAPACITY); + this.hpackEncoder = new HpackEncoder(initialTableSize); + this.headerEncodeBuffer = new ByteBufferOutputStream(512); + this.workerThread = Thread.ofVirtual().name(threadName).start(this::workerLoop); + } + + // ==================== LIFECYCLE ==================== + + /** + * Create a new exchange for a request. + */ + H2Exchange newExchange(HttpRequest request, long readTimeoutMs, long writeTimeoutMs) throws IOException { + if (!accepting) { + throw new IOException("Connection is not accepting new streams"); + } + + if (goawayReceived) { + int nextId = nextStreamId.get(); + if (nextId > goawayLastStreamId) { + throw new IOException("Connection received GOAWAY with lastStreamId=" + + goawayLastStreamId + ", cannot create stream " + nextId); + } + } + + if (!tryReserveStream()) { + throw new IOException("Connection at max concurrent streams: " + activeStreamCount.get() + + " (limit: " + remoteMaxConcurrentStreams + ")"); + } + + return new H2Exchange(this, request, readTimeoutMs, writeTimeoutMs); + } + + /** + * Close all exchanges gracefully. + */ + void closeExchanges(Duration timeout) { + accepting = false; + + streams.forEach(exchange -> exchange.signalConnectionClosed(null)); + + long deadline = System.nanoTime() + timeout.toNanos(); + while (activeStreamCount.get() > 0 && System.nanoTime() < deadline) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + + // Force close any remaining exchanges and clear slots + streams.clearAndClose(exchange -> { + try { + exchange.close(); + } catch (Exception ignored) {} + }); + activeStreamCount.set(0); + } + + H2Exchange getExchange(int streamId) { + return streams.get(streamId); + } + + int getActiveStreamCount() { + return activeStreamCount.get(); + } + + boolean canAcceptMoreStreams() { + return accepting && !goawayReceived && activeStreamCount.get() < remoteMaxConcurrentStreams; + } + + /** + * Get the active stream count if this muxer can accept more streams, or -1 if not. + * Combines the availability check with getting the count to avoid redundant atomic reads. + */ + int getActiveStreamCountIfAccepting() { + if (!accepting || goawayReceived) { + return -1; + } + int count = activeStreamCount.get(); + return count < remoteMaxConcurrentStreams ? count : -1; + } + + int getLastAllocatedStreamId() { + return lastAllocatedStreamId; + } + + private boolean tryReserveStream() { + while (true) { + int current = activeStreamCount.get(); + if (current >= remoteMaxConcurrentStreams) { + return false; + } + if (activeStreamCount.compareAndSet(current, current + 1)) { + return true; + } + } + } + + void releaseStream(int streamId) { + if (streams.remove(streamId)) { + activeStreamCount.decrementAndGet(); + } + } + + void releaseStreamSlot() { + activeStreamCount.decrementAndGet(); + } + + int allocateAndRegisterStream(H2Exchange exchange) { + int streamId = nextStreamId.getAndAdd(2); + exchange.setStreamId(streamId); + streams.put(streamId, exchange); + lastAllocatedStreamId = streamId; + return streamId; + } + + void onConnectionClosing(Throwable error) { + accepting = false; + streams.forEach(exchange -> exchange.signalConnectionClosed(error)); + } + + void onSettingsReceived(int maxConcurrentStreams, int initialWindowSize, int maxFrameSize) { + this.remoteMaxConcurrentStreams = maxConcurrentStreams; + this.remoteMaxFrameSize = maxFrameSize; + + int delta = initialWindowSize - this.remoteInitialWindowSize; + this.remoteInitialWindowSize = initialWindowSize; + if (delta != 0) { + streams.forEach(exchange -> exchange.adjustSendWindow(delta)); + } + } + + void onGoaway(int lastStreamId, int errorCode) { + goawayReceived = true; + goawayLastStreamId = lastStreamId; + accepting = false; + + H2Exception refusedError = new H2Exception( + errorCode, + "Stream affected by GOAWAY (lastStreamId=" + lastStreamId + + ", error=" + H2Constants.errorCodeName(errorCode) + ")"); + streams.forEachMatching( + streamId -> streamId > lastStreamId, + exchange -> exchange.signalConnectionClosed(refusedError)); + } + + // ==================== FLOW CONTROL ==================== + + void acquireConnectionWindow(int requestedBytes, long timeoutMs) + throws SocketTimeoutException, InterruptedException { + if (!connectionSendWindow.tryAcquire(requestedBytes, timeoutMs, TimeUnit.MILLISECONDS)) { + throw new SocketTimeoutException( + "Write timed out after " + timeoutMs + "ms waiting for connection flow control window"); + } + } + + void releaseConnectionWindow(int bytes) { + int currentWindow = connectionSendWindow.available(); + if ((long) currentWindow + bytes <= Integer.MAX_VALUE) { + connectionSendWindow.release(bytes); + } + } + + // ==================== WRITE QUEUE ==================== + + void signalDataReady(H2Exchange exchange) { + if (!accepting) { + return; + } + dataWorkQueue.offer(exchange); + if (!dataWorkPending) { + dataWorkPending = true; + workQueue.offer(CHECK_DATA_QUEUE); + } + } + + void queueControlFrame(int streamId, ControlFrameType frameType, Object payload, long timeoutMs) + throws IOException { + WorkItem item = switch (frameType) { + case RST_STREAM -> new WorkItem.WriteRst(streamId, (Integer) payload); + case WINDOW_UPDATE -> new WorkItem.WriteWindowUpdate(streamId, (Integer) payload); + case SETTINGS_ACK -> new WorkItem.WriteSettingsAck(); + case PING -> new WorkItem.WritePing((byte[]) payload, false); + case GOAWAY -> { + Object[] args = (Object[]) payload; + yield new WorkItem.WriteGoaway((Integer) args[0], (Integer) args[1], (String) args[2]); + } + }; + + try { + if (!workQueue.offer(item, timeoutMs, TimeUnit.MILLISECONDS)) { + throw new IOException("Work queue full, cannot queue control frame (timeout: " + timeoutMs + "ms)"); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Interrupted while queuing control frame", e); + } + } + + void queueTrailers(int streamId, HttpHeaders trailers) throws IOException { + WorkItem item = new WorkItem.WriteTrailers(streamId, trailers, new CompletableFuture<>()); + if (!workQueue.offer(item)) { + throw new IOException("Work queue full, cannot queue trailers"); + } + } + + void queueData(int streamId, byte[] data, int offset, int length, int flags) throws IOException { + WorkItem item = new WorkItem.WriteData(streamId, data, offset, length, flags, new CompletableFuture<>()); + if (!workQueue.offer(item)) { + throw new IOException("Work queue full, cannot queue data frame"); + } + } + + /** + * Submit a HEADERS frame for encoding and writing. + * + * @param request the HTTP request + * @param exchange the exchange + * @param endStream whether END_STREAM should be set + * @param streamIdFuture future completed with stream ID after allocation + * @param writeComplete future completed when write finishes + * @param timeoutMs timeout for queue submission + * @return true if submitted, false if queue full or not accepting + */ + boolean submitHeaders( + HttpRequest request, + H2Exchange exchange, + boolean endStream, + CompletableFuture streamIdFuture, + CompletableFuture writeComplete, + long timeoutMs + ) { + if (!accepting) { + return false; + } + var item = new WorkItem.EncodeHeaders(request, exchange, endStream, streamIdFuture, writeComplete); + try { + return workQueue.offer(item, timeoutMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + + // ==================== BUFFER POOL ==================== + + byte[] borrowBuffer(int minSize) { + return bufferPool.borrow(minSize); + } + + void returnBuffer(byte[] buffer) { + if (buffer != null) { + bufferPool.release(buffer); + } + } + + // ==================== SETTINGS ==================== + + int getRemoteMaxFrameSize() { + return remoteMaxFrameSize; + } + + int getRemoteInitialWindowSize() { + return remoteInitialWindowSize; + } + + void setMaxTableSize(int newSize) { + this.pendingTableSizeUpdate = newSize; + } + + IOException getWriteError() { + return writeError; + } + + // ==================== WRITER THREAD ==================== + + private void workerLoop() { + var batch = new ArrayList(64); + IOException failure = null; + + try { + while (running) { + WorkItem item = workQueue.take(); + + if (item instanceof WorkItem.Shutdown) { + return; + } + + if (!(item instanceof WorkItem.CheckDataQueue)) { + batch.add(item); + } + + while ((item = workQueue.poll()) != null) { + if (item instanceof WorkItem.Shutdown) { + processBatch(batch); + return; + } + if (!(item instanceof WorkItem.CheckDataQueue)) { + batch.add(item); + } + } + + if (!batch.isEmpty()) { + processBatch(batch); + } + + dataWorkPending = false; + + boolean processedData = false; + H2Exchange exchange; + while ((exchange = dataWorkQueue.poll()) != null) { + processExchangePendingWrites(exchange); + processedData = true; + } + + if (processedData) { + try { + frameCodec.flush(); + } catch (IOException e) { + failWriter(e); + return; + } + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + if (running) { + failure = new IOException("Writer thread interrupted", e); + } + } catch (Throwable t) { + failure = new IOException("Writer thread crashed", t); + } finally { + if (failure != null) { + failWriter(failure); + } else { + drainAndFailPending(new IOException("Muxer shutting down")); + } + } + } + + private void processExchangePendingWrites(H2Exchange exchange) { + exchange.inWorkQueue = false; + + int streamId = exchange.getStreamId(); + PendingWrite pw; + while ((pw = exchange.pendingWrites.poll()) != null) { + byte[] buffer = pw.data; + try { + frameCodec.writeFrame( + FRAME_TYPE_DATA, + pw.flags, + streamId, + pw.data, + pw.offset, + pw.length); + } catch (IOException e) { + exchange.returnBuffer(buffer); + failWriter(e); + return; + } + exchange.returnBuffer(buffer); + pw.reset(); + } + + if (!exchange.pendingWrites.isEmpty() && !exchange.inWorkQueue) { + exchange.inWorkQueue = true; + dataWorkQueue.offer(exchange); + } + } + + private void processBatch(ArrayList batch) { + if (batch.isEmpty()) { + return; + } + + try { + for (WorkItem item : batch) { + processItem(item); + } + frameCodec.flush(); + for (WorkItem item : batch) { + completeItem(item, null); + } + } catch (IOException e) { + for (WorkItem item : batch) { + completeItem(item, e); + } + } finally { + batch.clear(); + } + } + + private void processItem(WorkItem item) throws IOException { + switch (item) { + case WorkItem.EncodeHeaders h -> processEncodeHeaders(h); + case WorkItem.WriteData d -> + frameCodec.writeFrame(FRAME_TYPE_DATA, d.flags(), d.streamId(), d.data(), d.offset(), d.length()); + case WorkItem.WriteTrailers t -> processWriteTrailers(t); + case WorkItem.WriteRst r -> frameCodec.writeRstStream(r.streamId(), r.errorCode()); + case WorkItem.WriteGoaway g -> frameCodec.writeGoaway(g.lastStreamId(), g.errorCode(), g.debugData()); + case WorkItem.WriteWindowUpdate w -> frameCodec.writeWindowUpdate(w.streamId(), w.increment()); + case WorkItem.WriteSettingsAck s -> frameCodec.writeSettingsAck(); + case WorkItem.WritePing p -> frameCodec.writeFrame(FRAME_TYPE_PING, p.ack() ? FLAG_ACK : 0, 0, p.payload()); + case WorkItem.Shutdown s -> { + } + case WorkItem.CheckDataQueue c -> { + } + } + } + + private void processEncodeHeaders(WorkItem.EncodeHeaders req) throws IOException { + H2Exchange exchange = req.exchange(); + + try { + if (!connectionCallback.isAcceptingStreams()) { + req.streamIdFuture() + .completeExceptionally(new IOException("Connection is not accepting new streams")); + return; + } + + int streamId = allocateAndRegisterStream(exchange); + + int tableUpdate = pendingTableSizeUpdate; + if (tableUpdate >= 0) { + hpackEncoder.setMaxTableSize(tableUpdate); + pendingTableSizeUpdate = -1; + } + + byte[] headerBlock = encodeHeaders(req.request()); + + exchange.onHeadersEncoded(req.endStream()); + + frameCodec.writeHeaders(streamId, headerBlock, 0, headerBlock.length, req.endStream()); + + req.streamIdFuture().complete(streamId); + + } catch (Exception e) { + int streamId = exchange.getStreamId(); + if (streamId > 0) { + releaseStream(streamId); + } + if (e instanceof IOException || e instanceof H2Exception) { + req.streamIdFuture().completeExceptionally(e); + } else { + req.streamIdFuture().completeExceptionally(new IOException("Encoding failed", e)); + } + } + } + + private void processWriteTrailers(WorkItem.WriteTrailers req) throws IOException { + byte[] headerBlock = encodeTrailers(req.trailers()); + frameCodec.writeHeaders(req.streamId(), headerBlock, 0, headerBlock.length, true); + } + + private byte[] encodeHeaders(HttpRequest request) throws IOException { + headerEncodeBuffer.reset(); + hpackEncoder.beginHeaderBlock(headerEncodeBuffer); + + long headerListSize = 0; + String method = request.method(); + boolean isConnect = "CONNECT".equalsIgnoreCase(method); + + String authority = getAuthority(request); + String scheme = isConnect ? null : request.uri().getScheme(); + String path = isConnect ? null : getPath(request); + + hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_METHOD, method, false); + headerListSize += PSEUDO_METHOD.length() + method.length() + 32; + + if (!isConnect) { + hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_SCHEME, scheme, false); + headerListSize += PSEUDO_SCHEME.length() + (scheme != null ? scheme.length() : 0) + 32; + } + + hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_AUTHORITY, authority, false); + headerListSize += PSEUDO_AUTHORITY.length() + authority.length() + 32; + + if (!isConnect) { + hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_PATH, path, false); + headerListSize += PSEUDO_PATH.length() + path.length() + 32; + } + + for (var entry : request.headers()) { + String name = entry.getKey(); + if (CONNECTION_HEADERS.contains(name)) { + continue; + } + boolean isTe = "te".equals(name); + boolean sensitive = SENSITIVE_HEADERS.contains(name); + for (String value : entry.getValue()) { + if (isTe && !"trailers".equalsIgnoreCase(value)) { + continue; + } + hpackEncoder.encodeHeader(headerEncodeBuffer, name, value, sensitive); + headerListSize += name.length() + value.length() + 32; + } + } + + int maxSize = connectionCallback.getRemoteMaxHeaderListSize(); + if (maxSize != Integer.MAX_VALUE && headerListSize > maxSize) { + throw new IOException("Header list size (" + headerListSize + ") exceeds limit (" + maxSize + ")"); + } + + return finishHeaderBlock(); + } + + private byte[] encodeTrailers(HttpHeaders trailers) throws IOException { + headerEncodeBuffer.reset(); + hpackEncoder.beginHeaderBlock(headerEncodeBuffer); + + for (var entry : trailers) { + String name = entry.getKey(); + if (name.startsWith(":")) { + throw new IOException("Trailers must not contain pseudo-header: " + name); + } + boolean sensitive = SENSITIVE_HEADERS.contains(name); + for (String value : entry.getValue()) { + hpackEncoder.encodeHeader(headerEncodeBuffer, name, value, sensitive); + } + } + + return finishHeaderBlock(); + } + + private byte[] finishHeaderBlock() { + ByteBuffer buffer = headerEncodeBuffer.toByteBuffer(); + byte[] result = new byte[buffer.remaining()]; + buffer.get(result); + return result; + } + + private String getAuthority(HttpRequest request) { + String host = request.uri().getHost(); + int port = request.uri().getPort(); + String scheme = request.uri().getScheme(); + if (port == -1 || (port == 443 && "https".equalsIgnoreCase(scheme)) + || (port == 80 && "http".equalsIgnoreCase(scheme))) { + return host; + } + return host + ":" + port; + } + + private String getPath(HttpRequest request) { + String path = request.uri().getRawPath(); + if (path == null || path.isEmpty()) { + path = "/"; + } + String query = request.uri().getRawQuery(); + if (query != null && !query.isEmpty()) { + path = path + "?" + query; + } + return path; + } + + private void completeItem(WorkItem item, IOException error) { + CompletableFuture completion = switch (item) { + case WorkItem.EncodeHeaders h -> h.writeComplete(); + case WorkItem.WriteData d -> d.completion(); + case WorkItem.WriteTrailers t -> t.completion(); + case WorkItem.WriteRst r -> null; + case WorkItem.WriteGoaway g -> null; + case WorkItem.WriteWindowUpdate w -> null; + case WorkItem.WriteSettingsAck s -> null; + case WorkItem.WritePing p -> null; + case WorkItem.Shutdown s -> null; + case WorkItem.CheckDataQueue c -> null; + }; + if (completion != null) { + if (error == null) { + completion.complete(null); + } else { + completion.completeExceptionally(error); + } + } + } + + private void failWriter(IOException e) { + if (writeError == null) { + writeError = e; + } + accepting = false; + drainAndFailPending(writeError); + } + + private void drainAndFailPending(IOException error) { + WorkItem item; + while ((item = workQueue.poll()) != null) { + if (item instanceof WorkItem.EncodeHeaders h) { + h.streamIdFuture().completeExceptionally(error); + } + completeItem(item, error); + } + } + + @Override + public void close() { + accepting = false; + + long deadline = System.currentTimeMillis() + 1000; + while (!workQueue.isEmpty() && System.currentTimeMillis() < deadline) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + + running = false; + var _ignore = workQueue.offer(new WorkItem.Shutdown()); + + if (workerThread != null) { + workerThread.interrupt(); + try { + workerThread.join(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + drainAndFailPending(new IOException("Muxer shutting down")); + } + + void shutdownNow() { + accepting = false; + running = false; + var _ignore = workQueue.offer(new WorkItem.Shutdown()); + if (workerThread != null) { + workerThread.interrupt(); + } + drainAndFailPending(new IOException("Muxer shutting down")); + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamWriter.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamWriter.java deleted file mode 100644 index d69da459b..000000000 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamWriter.java +++ /dev/null @@ -1,570 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.h2; - -import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_REFUSED_STREAM; -import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_ACK; -import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_DATA; -import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_PING; -import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_AUTHORITY; -import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_METHOD; -import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_PATH; -import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_SCHEME; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Set; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import software.amazon.smithy.java.http.api.HttpHeaders; -import software.amazon.smithy.java.http.api.HttpRequest; -import software.amazon.smithy.java.http.client.h2.hpack.HpackEncoder; -import software.amazon.smithy.java.io.ByteBufferOutputStream; - -/** - * Combined HPACK encoder and frame writer for HTTP/2 connections. - * - *

      This class handles both header encoding AND frame writing in a single thread, - * eliminating the handoff overhead between separate encoder and writer threads. - * All socket writes are serialized through this thread. - * - *

      Thread Model

      - *
        - *
      • Caller threads submit work items to the queue
      • - *
      • Single thread processes requests: encode (if needed) → write → flush
      • - *
      • Batching: drains queue, processes all, flushes once
      • - *
      - * - *

      Ordering Guarantees

      - *
        - *
      • Stream IDs are allocated in submission order
      • - *
      • HPACK dynamic table updates happen in wire order
      • - *
      • Frames are written in submission order
      • - *
      - */ -final class H2StreamWriter implements AutoCloseable { - - /** - * Work items that can be submitted to the encoder/writer thread. - */ - sealed interface WorkItem { - /** Encode headers and write HEADERS frame for a new stream. */ - record EncodeHeaders( - HttpRequest request, - H2Exchange exchange, - boolean endStream, - CompletableFuture streamIdFuture, - CompletableFuture writeComplete) implements WorkItem {} - - /** Write a DATA frame. */ - record WriteData( - int streamId, - byte[] data, - int offset, - int length, - int flags, - CompletableFuture completion) implements WorkItem {} - - /** Encode and write trailer HEADERS frame with END_STREAM. */ - record WriteTrailers( - int streamId, - HttpHeaders trailers, - CompletableFuture completion) implements WorkItem {} - - /** Write a RST_STREAM frame. */ - record WriteRst( - int streamId, - int errorCode, - CompletableFuture completion) implements WorkItem {} - - /** Write a GOAWAY frame (fire-and-forget). */ - record WriteGoaway(int lastStreamId, int errorCode, String debugData) implements WorkItem {} - - /** Write a WINDOW_UPDATE frame (fire-and-forget). */ - record WriteWindowUpdate(int streamId, int increment) implements WorkItem {} - - /** Write a SETTINGS ACK frame (fire-and-forget). */ - record WriteSettingsAck() implements WorkItem {} - - /** Write a PING frame (fire-and-forget). */ - record WritePing(byte[] payload, boolean ack) implements WorkItem {} - - /** Shutdown marker. */ - record Shutdown() implements WorkItem {} - } - - /** - * Interface for encoder to manage streams on the connection. - */ - interface StreamManager { - boolean tryReserveStream(); - - void releaseStreamSlot(); - - void registerStream(int streamId, H2Exchange exchange); - - void unregisterStream(int streamId); - - void setLastStreamId(int streamId); - - boolean isAcceptingStreams(); - - int getRemoteMaxHeaderListSize(); - } - - // Headers that must not be sent over HTTP/2 (connection-specific) - private static final Set CONNECTION_HEADERS = Set.of( - "connection", - "keep-alive", - "proxy-connection", - "transfer-encoding", - "upgrade", - "host"); - - // Headers that should not be indexed in HPACK (contain sensitive data) - private static final Set SENSITIVE_HEADERS = Set.of( - "authorization", - "cookie", - "proxy-authorization", - "set-cookie"); - - private static final int QUEUE_CAPACITY = 1024; - - private final StreamManager streamManager; - private final H2FrameCodec frameCodec; - private final BlockingQueue workQueue; - private final Thread workerThread; - - // HPACK encoder state (only accessed by worker thread - no synchronization needed) - private final HpackEncoder hpackEncoder; - private final ByteBufferOutputStream headerEncodeBuffer; - - // Stream ID allocation (only accessed by worker thread) - private final AtomicInteger nextStreamId = new AtomicInteger(1); // Client uses odd IDs - - // Pending table size update (set by connection thread, read by worker thread) - private volatile int pendingTableSizeUpdate = -1; - - private volatile boolean running = true; - private volatile boolean accepting = true; - - /** - * Create a new combined encoder/writer. - * - * @param streamManager the stream manager for connection interaction - * @param frameCodec the frame codec for writing to socket - * @param initialTableSize initial HPACK dynamic table size - * @param threadName name for the worker thread - */ - H2StreamWriter(StreamManager streamManager, H2FrameCodec frameCodec, int initialTableSize, String threadName) { - this.streamManager = streamManager; - this.frameCodec = frameCodec; - this.workQueue = new LinkedBlockingQueue<>(QUEUE_CAPACITY); - this.hpackEncoder = new HpackEncoder(initialTableSize); - this.headerEncodeBuffer = new ByteBufferOutputStream(512); - this.workerThread = Thread.ofVirtual().name(threadName).start(this::workerLoop); - } - - /** - * Submit a work item with timeout (blocking if queue is full). - */ - boolean submitWork(WorkItem item, long timeoutMs) { - if (!accepting) { - return false; - } - try { - return workQueue.offer(item, timeoutMs, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return false; - } - } - - /** - * Submit a control frame (fire-and-forget, non-blocking). - */ - boolean submitControlFrame(WorkItem item) { - if (!accepting) { - return false; - } - return workQueue.offer(item); - } - - /** - * Update the HPACK encoder's max table size. - */ - void setMaxTableSize(int newSize) { - this.pendingTableSizeUpdate = newSize; - } - - /** - * Get the current stream ID counter value. - */ - int getNextStreamId() { - return nextStreamId.get(); - } - - /** - * Main worker loop - processes work items with batching. - */ - private void workerLoop() { - var batch = new ArrayList(64); - - try { - while (running) { - // Block for the first request - WorkItem item = workQueue.take(); - - if (item instanceof WorkItem.Shutdown) { - break; - } - - batch.add(item); - - // Drain opportunistically (non-blocking) for batching - while ((item = workQueue.poll()) != null) { - if (item instanceof WorkItem.Shutdown) { - processBatch(batch); - return; - } - batch.add(item); - } - - processBatch(batch); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - failRemainingRequests(); - } - - /** - * Process a batch of work items with a single flush at the end. - */ - private void processBatch(ArrayList batch) { - if (batch.isEmpty()) { - return; - } - - try { - // Process all items (encode if needed, write frames) - for (WorkItem item : batch) { - processItem(item); - } - - // Single flush for entire batch - frameCodec.flush(); - - // Complete futures for successful writes - for (WorkItem item : batch) { - completeItem(item, null); - } - } catch (IOException e) { - // Fail all items in batch - for (WorkItem item : batch) { - completeItem(item, e); - } - } finally { - batch.clear(); - } - } - - /** - * Process a single work item. - */ - private void processItem(WorkItem item) throws IOException { - switch (item) { - case WorkItem.EncodeHeaders h -> processEncodeHeaders(h); - case WorkItem.WriteData d -> - frameCodec.writeFrame(FRAME_TYPE_DATA, d.flags(), d.streamId(), d.data(), d.offset(), d.length()); - case WorkItem.WriteTrailers t -> processWriteTrailers(t); - case WorkItem.WriteRst r -> - frameCodec.writeRstStream(r.streamId(), r.errorCode()); - case WorkItem.WriteGoaway g -> - frameCodec.writeGoaway(g.lastStreamId(), g.errorCode(), g.debugData()); - case WorkItem.WriteWindowUpdate w -> - frameCodec.writeWindowUpdate(w.streamId(), w.increment()); - case WorkItem.WriteSettingsAck s -> - frameCodec.writeSettingsAck(); - case WorkItem.WritePing p -> - frameCodec.writeFrame(FRAME_TYPE_PING, p.ack() ? FLAG_ACK : 0, 0, p.payload()); - case WorkItem.Shutdown s -> { - // handled by caller - } - } - } - - /** - * Process trailer headers: encode via HPACK and write HEADERS frame with END_STREAM. - * - *

      Unlike request headers, trailers: - *

        - *
      • Use an existing stream ID (no allocation)
      • - *
      • Must NOT contain pseudo-headers
      • - *
      • Always have END_STREAM flag set
      • - *
      - */ - private void processWriteTrailers(WorkItem.WriteTrailers req) throws IOException { - byte[] headerBlock = encodeTrailers(req.trailers()); - frameCodec.writeHeaders(req.streamId(), headerBlock, 0, headerBlock.length, true); - } - - /** - * Encode trailer headers using HPACK. - * - *

      Trailers must not contain pseudo-headers (names starting with ':'). - */ - private byte[] encodeTrailers(HttpHeaders trailers) throws IOException { - headerEncodeBuffer.reset(); - hpackEncoder.beginHeaderBlock(headerEncodeBuffer); - - for (var entry : trailers) { - String name = entry.getKey(); - if (name.startsWith(":")) { - throw new IOException("Trailers must not contain pseudo-header: " + name); - } - boolean sensitive = SENSITIVE_HEADERS.contains(name); - for (String value : entry.getValue()) { - hpackEncoder.encodeHeader(headerEncodeBuffer, name, value, sensitive); - } - } - - ByteBuffer buffer = headerEncodeBuffer.toByteBuffer(); - byte[] result = new byte[buffer.remaining()]; - buffer.get(result); - return result; - } - - /** - * Process an encode headers request: allocate stream, encode HPACK, write frame. - */ - private void processEncodeHeaders(WorkItem.EncodeHeaders req) throws IOException { - int streamId = -1; - boolean slotReserved = false; - boolean streamRegistered = false; - - try { - if (!streamManager.isAcceptingStreams()) { - req.streamIdFuture() - .completeExceptionally( - new IOException("Connection is not accepting new streams")); - return; - } - - if (!streamManager.tryReserveStream()) { - req.streamIdFuture() - .completeExceptionally( - new IOException("Connection at max concurrent streams")); - return; - } - slotReserved = true; - - streamId = nextStreamId.getAndAdd(2); - if (streamId < 0) { - req.streamIdFuture() - .completeExceptionally( - new H2Exception(ERROR_REFUSED_STREAM, "Stream ID space exhausted")); - streamManager.releaseStreamSlot(); - return; - } - - streamManager.setLastStreamId(streamId); - req.exchange().setStreamId(streamId); - streamManager.registerStream(streamId, req.exchange()); - streamRegistered = true; - req.exchange().onHeadersEncoded(req.endStream()); - - int tableUpdate = pendingTableSizeUpdate; - if (tableUpdate >= 0) { - hpackEncoder.setMaxTableSize(tableUpdate); - pendingTableSizeUpdate = -1; - } - - byte[] headerBlock = encodeHeaders(req.request()); - - // Write directly - no intermediate queue! - frameCodec.writeHeaders(streamId, headerBlock, 0, headerBlock.length, req.endStream()); - - req.streamIdFuture().complete(streamId); - - } catch (Exception e) { - if (streamRegistered) { - // unregisterStream handles both map removal AND count decrement - streamManager.unregisterStream(streamId); - } else if (slotReserved) { - // Slot reserved but stream not registered - just release the slot - streamManager.releaseStreamSlot(); - } - if (e instanceof IOException || e instanceof H2Exception) { - req.streamIdFuture().completeExceptionally(e); - } else { - req.streamIdFuture().completeExceptionally(new IOException("Encoding failed", e)); - } - } - } - - /** - * Complete a work item's future. - */ - private void completeItem(WorkItem item, IOException error) { - CompletableFuture completion = switch (item) { - case WorkItem.EncodeHeaders h -> h.writeComplete(); - case WorkItem.WriteData d -> d.completion(); - case WorkItem.WriteTrailers t -> t.completion(); - case WorkItem.WriteRst r -> r.completion(); - case WorkItem.WriteGoaway g -> null; - case WorkItem.WriteWindowUpdate w -> null; - case WorkItem.WriteSettingsAck s -> null; - case WorkItem.WritePing p -> null; - case WorkItem.Shutdown s -> null; - }; - if (completion != null) { - if (error == null) { - completion.complete(null); - } else { - completion.completeExceptionally(error); - } - } - } - - /** - * Encode headers using HPACK. - */ - private byte[] encodeHeaders(HttpRequest request) throws IOException { - headerEncodeBuffer.reset(); - hpackEncoder.beginHeaderBlock(headerEncodeBuffer); - - long headerListSize = 0; - String method = request.method(); - boolean isConnect = "CONNECT".equalsIgnoreCase(method); - - String authority = getAuthority(request); - String scheme = isConnect ? null : request.uri().getScheme(); - String path = isConnect ? null : getPath(request); - - hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_METHOD, method, false); - headerListSize += PSEUDO_METHOD.length() + method.length() + 32; - - if (!isConnect) { - hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_SCHEME, scheme, false); - headerListSize += PSEUDO_SCHEME.length() + (scheme != null ? scheme.length() : 0) + 32; - } - - hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_AUTHORITY, authority, false); - headerListSize += PSEUDO_AUTHORITY.length() + authority.length() + 32; - - if (!isConnect) { - hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_PATH, path, false); - headerListSize += PSEUDO_PATH.length() + path.length() + 32; - } - - for (var entry : request.headers()) { - String name = entry.getKey(); - if (CONNECTION_HEADERS.contains(name)) { - continue; - } - boolean isTe = "te".equals(name); - boolean sensitive = SENSITIVE_HEADERS.contains(name); - for (String value : entry.getValue()) { - if (isTe && !"trailers".equalsIgnoreCase(value)) { - continue; - } - hpackEncoder.encodeHeader(headerEncodeBuffer, name, value, sensitive); - headerListSize += name.length() + value.length() + 32; - } - } - - int maxSize = streamManager.getRemoteMaxHeaderListSize(); - if (maxSize != Integer.MAX_VALUE && headerListSize > maxSize) { - throw new IOException("Header list size (" + headerListSize + - ") exceeds limit (" + maxSize + ")"); - } - - ByteBuffer buffer = headerEncodeBuffer.toByteBuffer(); - byte[] result = new byte[buffer.remaining()]; - buffer.get(result); - return result; - } - - private String getAuthority(HttpRequest request) { - String host = request.uri().getHost(); - int port = request.uri().getPort(); - String scheme = request.uri().getScheme(); - if (port == -1 || (port == 443 && "https".equalsIgnoreCase(scheme)) - || (port == 80 && "http".equalsIgnoreCase(scheme))) { - return host; - } - return host + ":" + port; - } - - private String getPath(HttpRequest request) { - String path = request.uri().getRawPath(); - if (path == null || path.isEmpty()) { - path = "/"; - } - String query = request.uri().getRawQuery(); - if (query != null && !query.isEmpty()) { - path = path + "?" + query; - } - return path; - } - - private void failRemainingRequests() { - IOException shutdownError = new IOException("Encoder shutting down"); - WorkItem item; - while ((item = workQueue.poll()) != null) { - if (item instanceof WorkItem.EncodeHeaders h) { - h.streamIdFuture().completeExceptionally(shutdownError); - } - completeItem(item, shutdownError); - } - } - - @Override - public void close() { - accepting = false; - - long deadline = System.currentTimeMillis() + 1000; - while (!workQueue.isEmpty() && System.currentTimeMillis() < deadline) { - try { - Thread.sleep(10); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - } - - running = false; - var _ignore = workQueue.offer(new WorkItem.Shutdown()); - - if (workerThread != null) { - workerThread.interrupt(); - try { - workerThread.join(100); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - failRemainingRequests(); - } - - void shutdownNow() { - accepting = false; - running = false; - var _ignore = workQueue.offer(new WorkItem.Shutdown()); - if (workerThread != null) { - workerThread.interrupt(); - } - - failRemainingRequests(); - } -} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/PendingWrite.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/PendingWrite.java new file mode 100644 index 000000000..0792cdf3a --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/PendingWrite.java @@ -0,0 +1,64 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +/** + * A pending DATA frame write queued for the writer thread. + */ +final class PendingWrite { + /** + * The data buffer (borrowed from BufferPool). + */ + byte[] data; + + /** + * Offset within the data buffer. + */ + int offset; + + /** + * Length of data to write. + */ + int length; + + /** + * Whether this write has the END_STREAM flag. + */ + boolean endStream; + + /** + * Frame flags (e.g., END_STREAM). + */ + int flags; + + /** + * Initialize this pending write with data. + * + * @param data the data buffer + * @param offset offset within buffer + * @param length length to write + * @param endStream whether this is the last write + * @param flags frame flags + */ + void init(byte[] data, int offset, int length, boolean endStream, int flags) { + this.data = data; + this.offset = offset; + this.length = length; + this.endStream = endStream; + this.flags = flags; + } + + /** + * Reset this instance for reuse. + */ + void reset() { + this.data = null; + this.offset = 0; + this.length = 0; + this.endStream = false; + this.flags = 0; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamEvent.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamEvent.java deleted file mode 100644 index 40f407ebc..000000000 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamEvent.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.h2; - -import java.io.IOException; -import java.util.List; -import software.amazon.smithy.java.http.client.h2.hpack.HpackDecoder; - -/** - * Events that can occur on an HTTP/2 stream. - * - *

      This sealed interface represents all possible events that the connection's - * reader thread can deliver to a stream. Using a single event queue with typed - * events simplifies the exchange implementation: - *

        - *
      • Single source of truth - one queue instead of multiple queues + side channels
      • - *
      • Errors as events - no volatile flags, errors flow through the same queue
      • - *
      • Explicit ordering - events arrive in wire order
      • - *
      • HPACK safety - headers are pre-decoded by reader thread before becoming events
      • - *
      - */ -sealed interface StreamEvent { - /** - * Pre-decoded response headers from the reader thread. - * - *

      HPACK decoding happens in the reader thread to ensure dynamic table - * updates are processed in frame order across all streams on the connection. - * - * @param fields the decoded header fields - * @param endStream true if FLAG_END_STREAM was set on the HEADERS frame - */ - record Headers(List fields, boolean endStream) implements StreamEvent {} - - /** - * Per-stream error (RST_STREAM received, protocol error, etc.). - */ - record StreamError(H2Exception cause) implements StreamEvent {} - - /** - * Connection-level error (GOAWAY, I/O error, etc.). - */ - record ConnectionError(IOException cause) implements StreamEvent {} - - /** - * Data chunk from response DATA frame. - * - *

      Implements {@link StreamEvent} to participate in the unified event queue. - * This avoids wrapper allocations on the hot data path. - * - *

      Invariants: - *

        - *
      • Non-end chunks always have {@code endStream == false}
      • - *
      • Only the {@link #END} sentinel or a trailing chunk has {@code endStream == true}
      • - *
      • {@code data} is null only for the {@link #END} sentinel
      • - *
      - */ - record DataChunk(byte[] data, int offset, int length, boolean endStream) implements StreamEvent { - private static final byte[] EMPTY_BYTES = new byte[0]; - - /** Sentinel for end of stream. */ - static final DataChunk END = new DataChunk(null, 0, 0, true); - - /** Singleton empty chunk for empty DATA frames without END_STREAM. */ - static final DataChunk EMPTY = new DataChunk(EMPTY_BYTES, 0, 0, false); - - boolean isEnd() { - return this == END || endStream; - } - } -} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamRegistry.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamRegistry.java new file mode 100644 index 000000000..33d7a0b40 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamRegistry.java @@ -0,0 +1,175 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.function.Consumer; + +/** + * Essentially a very fast custom hashmap of stream ID to H2Exchange. + * + *

      Architecture: + *

        + *
      • L1 Cache (Array): A fast, direct-mapped AtomicReferenceArray. 99% of streams live here. + *
      • L2 Storage (Map): A ConcurrentHashMap spillover. If the array slot for a new stream is already + * occupied (by a long-lived stream), the new stream goes here.
      • + *
      + * + *

      This guarantees that "Lapping" (ID wrap-around) never causes data loss, even if a stream stays open indefinitely + * while millions of others cycle through. + * + *

      HTTP/2 client stream IDs have useful properties we exploit: + *

        + *
      • Always odd numbers: 1, 3, 5, 7, ...
      • + *
      • Monotonically increasing (never reused on same connection)
      • + *
      + * + *

      We map stream IDs to array slots via: {@code slot = ((streamId - 1) >>> 1) & slotMask}. This gives O(1) lookup + * without hashing or Integer boxing overhead. + */ +final class StreamRegistry { + + // 4096 slots covers normal concurrency (100-1000) with ample headroom. + // Memory cost: 4096 * 4 bytes (ref) = 16KB per connection. + private static final int SLOTS = 4096; + private static final int SLOT_MASK = SLOTS - 1; + + private final AtomicReferenceArray fastPath = new AtomicReferenceArray<>(SLOTS); + private final ConcurrentHashMap spillover = new ConcurrentHashMap<>(); + + /** + * Map stream ID to slot index. + * Stream IDs are odd (1, 3, 5, ...), so we divide by 2 to get compact indices. + */ + private static int streamIdToSlot(int streamId) { + return ((streamId - 1) >>> 1) & SLOT_MASK; + } + + /** + * Register a new exchange. + * + *

      If the array slot is empty, the exchange goes there (fast path). If the slot is occupied by a long-lived + * stream, the new exchange spills over to the ConcurrentHashMap as a safety net. + * + * @param streamId the stream ID + * @param exchange the exchange to register + */ + void put(int streamId, H2Exchange exchange) { + int slot = streamIdToSlot(streamId); + + // Optimistic: Try to put in the fast array + H2Exchange existing = fastPath.get(slot); + + if (existing == null) { + // Slot is empty, claim it. + fastPath.set(slot, exchange); + } else { + // Collision: the slot is taken by an older, long-lived stream. Don't overwrite it, rather spill over. + spillover.put(streamId, exchange); + } + } + + /** + * Get an exchange by stream ID. + * + *

      First checks the fast array path, then falls back to the spillover map if there's a stream ID mismatch + * (indicating the stream was spilled). + * + * @param streamId the stream ID + * @return the exchange, or null if not found + */ + H2Exchange get(int streamId) { + int slot = streamIdToSlot(streamId); + H2Exchange exchange = fastPath.get(slot); + return exchange != null && exchange.getStreamId() == streamId ? exchange : spillover.get(streamId); + } + + /** + * Remove an exchange from the registry. + * + * @param streamId the stream ID + * @return true if the exchange was removed, false if not found + */ + boolean remove(int streamId) { + int slot = streamIdToSlot(streamId); + H2Exchange exchange = fastPath.get(slot); + + // Check Fast Path + if (exchange != null && exchange.getStreamId() == streamId) { + // CAS ensures we don't delete a NEW stream that just claimed the slot + return fastPath.compareAndSet(slot, exchange, null); + } + + // Check Slow Path + return spillover.remove(streamId) != null; + } + + /** + * Iterate over all active exchanges. + * Used for cold paths (cleanup, settings changes, connection close). + * + * @param action the action to perform on each exchange + */ + void forEach(Consumer action) { + // Iterate Array and the spillover map + for (int i = 0; i < SLOTS; i++) { + H2Exchange exchange = fastPath.get(i); + if (exchange != null) { + action.accept(exchange); + } + } + + if (!spillover.isEmpty()) { + spillover.values().forEach(action); + } + } + + /** + * Iterate over exchanges matching a predicate. + * + * @param predicate condition to check + * @param action the action to perform on matching exchanges + */ + void forEachMatching(java.util.function.IntPredicate predicate, Consumer action) { + // Iterate Array and spillover map. + for (int i = 0; i < SLOTS; i++) { + H2Exchange exchange = fastPath.get(i); + if (exchange != null && predicate.test(exchange.getStreamId())) { + action.accept(exchange); + } + } + + if (!spillover.isEmpty()) { + for (H2Exchange exchange : spillover.values()) { + if (predicate.test(exchange.getStreamId())) { + action.accept(exchange); + } + } + } + } + + /** + * Clear all slots and close exchanges. + * + * @param closeAction action to run on each exchange during clear + */ + void clearAndClose(Consumer closeAction) { + for (int i = 0; i < SLOTS; i++) { + H2Exchange exchange = fastPath.getAndSet(i, null); + if (exchange != null) { + closeAction.accept(exchange); + } + } + + if (!spillover.isEmpty()) { + for (H2Exchange exchange : spillover.values()) { + closeAction.accept(exchange); + } + spillover.clear(); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTable.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTable.java index fc04f11dc..d8100b971 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTable.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTable.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.http.client.h2.hpack; -import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.Deque; @@ -87,6 +86,9 @@ int maxSize() { * @param newMaxSize new maximum size in bytes */ void setMaxSize(int newMaxSize) { + if (newMaxSize == maxSize) { + return; + } this.maxSize = newMaxSize; evictToSize(newMaxSize); } @@ -190,13 +192,11 @@ void clear() { * Calculate the size of an entry per RFC 7541 Section 4.1. * Size = length(name) in octets + length(value) in octets + 32 * - *

      Uses ISO-8859-1 encoding as per HPACK string literal spec (RFC 7541 Section 5.2). + *

      HTTP/2 header names are lowercase ASCII, and values are effectively ASCII/Latin-1, + * so we count chars as bytes directly (avoids getBytes allocation per insertion). */ static int entrySize(String name, String value) { - // RFC 7541 uses ISO-8859-1 encoding - must count bytes, not characters - return name.getBytes(StandardCharsets.ISO_8859_1).length - + value.getBytes(StandardCharsets.ISO_8859_1).length - + ENTRY_OVERHEAD; + return name.length() + value.length() + ENTRY_OVERHEAD; } private void evictToSize(int targetSize) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HeaderField.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HeaderField.java new file mode 100644 index 000000000..24c774903 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HeaderField.java @@ -0,0 +1,17 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2.hpack; + +/** + * Decoded HPACK header field (name-value pair). + * + *

      This is the public type returned by HPACK decoding operations. + * Static table entries are pre-allocated and reused (zero allocation on lookup). + * + * @param name header field name (lowercase for HTTP/2) + * @param value header field value + */ +public record HeaderField(String name, String value) {} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java index 8c229e07a..08aa2f57f 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import software.amazon.smithy.java.http.api.HeaderNames; /** * HPACK decoder for HTTP/2 header decompression (RFC 7541). @@ -24,14 +25,6 @@ public final class HpackDecoder { /** Current position during decoding, reset at start of each decodeBlock call. */ private int decodePos; - /** - * Decoded header field. - * - * @param name Name of the header. - * @param value Value of the header. - */ - public record HeaderField(String name, String value) {} - /** * Create a decoder with the given maximum dynamic table size. * @@ -94,7 +87,7 @@ private HeaderField getIndexedField(int index) throws IOException { } if (index <= StaticTable.SIZE) { - return new HeaderField(StaticTable.getName(index), StaticTable.getValue(index)); + return StaticTable.get(index); } else { DynamicTable.HeaderField field = dynamicTable.get(index); return new HeaderField(field.name(), field.value()); @@ -110,7 +103,7 @@ private String getIndexedName(int index) throws IOException { } if (index <= StaticTable.SIZE) { - return StaticTable.getName(index); + return StaticTable.get(index).name(); } else { return dynamicTable.get(index).name(); } @@ -159,10 +152,9 @@ private String decodeString(byte[] data) throws IOException { if (decodePos >= data.length) { throw new IOException("Incomplete HPACK string: no data at position " + decodePos); } - boolean huffman = (data[decodePos] & 0x80) != 0; + boolean huffman = (data[decodePos] & 0x80) != 0; int length = decodeInteger(data, 7); - if (decodePos + length > data.length) { throw new IOException("HPACK string length exceeds buffer"); } @@ -179,17 +171,73 @@ private String decodeString(byte[] data) throws IOException { } /** - * Decode a literal header field. - * Updates decodePos and returns the decoded header field. + * Decode a header name string and intern strings. Updates decodePos and returns the interned name. + * + *

      NOTE: This method does NOT validate for uppercase. The caller must validate literal names using + * {@link #validateHeaderName(String)} on the raw decoded string BEFORE this method is called, + * to comply with RFC 9113 Section 8.2. + * + * @param data HPACK data buffer + * @param validate if true, validate for uppercase before interning + * @return interned header name + * @throws IOException if validation fails or decoding fails + */ + private String decodeHeaderName(byte[] data, boolean validate) throws IOException { + if (decodePos >= data.length) { + throw new IOException("Incomplete HPACK string: no data at position " + decodePos); + } + + boolean huffman = (data[decodePos] & 0x80) != 0; + int length = decodeInteger(data, 7); + + if (decodePos + length > data.length) { + throw new IOException("HPACK string length exceeds buffer"); + } + + String name; + if (huffman) { + // Huffman-encoded: decode then validate and normalize + name = Huffman.decode(data, decodePos, length); + if (validate) { + validateHeaderName(name); + } + name = HeaderNames.canonicalize(name); + } else { + // Raw bytes: validate before interning if needed + if (validate) { + for (int i = 0; i < length; i++) { + byte b = data[decodePos + i]; + if (b >= 'A' && b <= 'Z') { + throw new IOException("Header field name contains uppercase character"); + } + } + } + // Normalize directly (zero-copy for known headers) + name = HeaderNames.canonicalize(data, decodePos, length); + } + decodePos += length; + + return name; + } + + /** + * Decode a literal header field. Updates decodePos and returns the decoded header field. + * + *

      Validates literal header names per RFC 9113 Section 8.2. Indexed names are already validated + * (static table is RFC-defined, dynamic table entries were validated when added). + * + *

      Literal names are interned via {@link HeaderNames} for efficient pointer comparisons. */ private HeaderField decodeLiteralField(byte[] data, int prefixBits) throws IOException { int nameIndex = decodeInteger(data, prefixBits); String name; if (nameIndex > 0) { + // Indexed name, so already validated (static or dynamic table) name = getIndexedName(nameIndex); } else { - name = decodeString(data); + // Literal name: decode with validation (must not contain uppercase per RFC 9113) + name = decodeHeaderName(data, true); } return new HeaderField(name, decodeString(data)); @@ -205,7 +253,8 @@ private HeaderField decodeLiteralField(byte[] data, int prefixBits) throws IOExc * @throws IOException if decoding fails */ public List decodeBlock(byte[] data, int offset, int length) throws IOException { - List headers = new ArrayList<>(); + // Initial capacity of 12 avoids resizing for typical HTTP responses (5-15 headers) + List headers = new ArrayList<>(12); decodePos = offset; int end = offset + length; int totalSize = 0; @@ -217,11 +266,14 @@ public List decodeBlock(byte[] data, int offset, int length) throws HeaderField field; if ((b & 0x80) != 0) { // Indexed representation: 1xxxxxxx + // No validation needed - static table is RFC-defined lowercase, + // dynamic table entries were validated when added int index = decodeInteger(data, 7); field = getIndexedField(index); headerFieldSeen = true; } else if ((b & 0x40) != 0) { // Literal with indexing: 01xxxxxx + // decodeLiteralField validates literal names field = decodeLiteralField(data, 6); dynamicTable.add(field.name(), field.value()); headerFieldSeen = true; @@ -236,13 +288,11 @@ public List decodeBlock(byte[] data, int offset, int length) throws continue; } else { // Literal never indexed (0001xxxx) or without indexing (0000xxxx) + // decodeLiteralField validates literal names field = decodeLiteralField(data, 4); headerFieldSeen = true; } - // RFC 9113 Section 8.2: Field names MUST NOT contain uppercase characters - validateHeaderName(field.name()); - // Check header list size per RFC 7541 Section 4.1. // Since HPACK-decoded strings are ISO-8859-1, each char is one byte, // so we can use length() directly instead of allocating byte arrays. diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTable.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTable.java index b8b77891d..8087c299c 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTable.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTable.java @@ -5,6 +5,8 @@ package software.amazon.smithy.java.http.client.h2.hpack; +import software.amazon.smithy.java.http.api.HeaderNames; + /** * HPACK static table from RFC 7541 Appendix A. * @@ -13,6 +15,8 @@ *

      This implementation uses length-based bucketing for fast lookups with zero per-lookup * allocations. Entries are grouped by header name length, so lookups only scan candidates * with matching name length (typically 1-3 entries per bucket). + * + *

      Header names use constants from {@link HeaderNames} to enable pointer comparisons. */ final class StaticTable { @@ -24,72 +28,74 @@ private StaticTable() {} static final int SIZE = 61; /** - * Static table entries. Index 0 is unused (indices are 1-based). - * Each entry is {name, value} where value may be empty string. + * Static table entries as pre-allocated HeaderField instances. + * Index 0 is unused (indices are 1-based per RFC 7541). + * + *

      Names use HeaderNameRegistry constants for pointer equality. */ - private static final String[][] ENTRIES = { + private static final HeaderField[] ENTRIES = { null, // Index 0 unused - {":authority", ""}, // 1 - {":method", "GET"}, // 2 - {":method", "POST"}, // 3 - {":path", "/"}, // 4 - {":path", "/index.html"}, // 5 - {":scheme", "http"}, // 6 - {":scheme", "https"}, // 7 - {":status", "200"}, // 8 - {":status", "204"}, // 9 - {":status", "206"}, // 10 - {":status", "304"}, // 11 - {":status", "400"}, // 12 - {":status", "404"}, // 13 - {":status", "500"}, // 14 - {"accept-charset", ""}, // 15 - {"accept-encoding", "gzip, deflate"}, // 16 - {"accept-language", ""}, // 17 - {"accept-ranges", ""}, // 18 - {"accept", ""}, // 19 - {"access-control-allow-origin", ""}, // 20 - {"age", ""}, // 21 - {"allow", ""}, // 22 - {"authorization", ""}, // 23 - {"cache-control", ""}, // 24 - {"content-disposition", ""}, // 25 - {"content-encoding", ""}, // 26 - {"content-language", ""}, // 27 - {"content-length", ""}, // 28 - {"content-location", ""}, // 29 - {"content-range", ""}, // 30 - {"content-type", ""}, // 31 - {"cookie", ""}, // 32 - {"date", ""}, // 33 - {"etag", ""}, // 34 - {"expect", ""}, // 35 - {"expires", ""}, // 36 - {"from", ""}, // 37 - {"host", ""}, // 38 - {"if-match", ""}, // 39 - {"if-modified-since", ""}, // 40 - {"if-none-match", ""}, // 41 - {"if-range", ""}, // 42 - {"if-unmodified-since", ""}, // 43 - {"last-modified", ""}, // 44 - {"link", ""}, // 45 - {"location", ""}, // 46 - {"max-forwards", ""}, // 47 - {"proxy-authenticate", ""}, // 48 - {"proxy-authorization", ""}, // 49 - {"range", ""}, // 50 - {"referer", ""}, // 51 - {"refresh", ""}, // 52 - {"retry-after", ""}, // 53 - {"server", ""}, // 54 - {"set-cookie", ""}, // 55 - {"strict-transport-security", ""}, // 56 - {"transfer-encoding", ""}, // 57 - {"user-agent", ""}, // 58 - {"vary", ""}, // 59 - {"via", ""}, // 60 - {"www-authenticate", ""} // 61 + new HeaderField(HeaderNames.PSEUDO_AUTHORITY, ""), // 1 + new HeaderField(HeaderNames.PSEUDO_METHOD, "GET"), // 2 + new HeaderField(HeaderNames.PSEUDO_METHOD, "POST"), // 3 + new HeaderField(HeaderNames.PSEUDO_PATH, "/"), // 4 + new HeaderField(HeaderNames.PSEUDO_PATH, "/index.html"), // 5 + new HeaderField(HeaderNames.PSEUDO_SCHEME, "http"), // 6 + new HeaderField(HeaderNames.PSEUDO_SCHEME, "https"), // 7 + new HeaderField(HeaderNames.PSEUDO_STATUS, "200"), // 8 + new HeaderField(HeaderNames.PSEUDO_STATUS, "204"), // 9 + new HeaderField(HeaderNames.PSEUDO_STATUS, "206"), // 10 + new HeaderField(HeaderNames.PSEUDO_STATUS, "304"), // 11 + new HeaderField(HeaderNames.PSEUDO_STATUS, "400"), // 12 + new HeaderField(HeaderNames.PSEUDO_STATUS, "404"), // 13 + new HeaderField(HeaderNames.PSEUDO_STATUS, "500"), // 14 + new HeaderField(HeaderNames.ACCEPT_CHARSET, ""), // 15 + new HeaderField(HeaderNames.ACCEPT_ENCODING, "gzip, deflate"), // 16 + new HeaderField(HeaderNames.ACCEPT_LANGUAGE, ""), // 17 + new HeaderField(HeaderNames.ACCEPT_RANGES, ""), // 18 + new HeaderField(HeaderNames.ACCEPT, ""), // 19 + new HeaderField(HeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, ""), // 20 + new HeaderField(HeaderNames.AGE, ""), // 21 + new HeaderField(HeaderNames.ALLOW, ""), // 22 + new HeaderField(HeaderNames.AUTHORIZATION, ""), // 23 + new HeaderField(HeaderNames.CACHE_CONTROL, ""), // 24 + new HeaderField(HeaderNames.CONTENT_DISPOSITION, ""), // 25 + new HeaderField(HeaderNames.CONTENT_ENCODING, ""), // 26 + new HeaderField(HeaderNames.CONTENT_LANGUAGE, ""), // 27 + new HeaderField(HeaderNames.CONTENT_LENGTH, ""), // 28 + new HeaderField(HeaderNames.CONTENT_LOCATION, ""), // 29 + new HeaderField(HeaderNames.CONTENT_RANGE, ""), // 30 + new HeaderField(HeaderNames.CONTENT_TYPE, ""), // 31 + new HeaderField(HeaderNames.COOKIE, ""), // 32 + new HeaderField(HeaderNames.DATE, ""), // 33 + new HeaderField(HeaderNames.ETAG, ""), // 34 + new HeaderField(HeaderNames.EXPECT, ""), // 35 + new HeaderField(HeaderNames.EXPIRES, ""), // 36 + new HeaderField(HeaderNames.FROM, ""), // 37 + new HeaderField(HeaderNames.HOST, ""), // 38 + new HeaderField(HeaderNames.IF_MATCH, ""), // 39 + new HeaderField(HeaderNames.IF_MODIFIED_SINCE, ""), // 40 + new HeaderField(HeaderNames.IF_NONE_MATCH, ""), // 41 + new HeaderField(HeaderNames.IF_RANGE, ""), // 42 + new HeaderField(HeaderNames.IF_UNMODIFIED_SINCE, ""), // 43 + new HeaderField(HeaderNames.LAST_MODIFIED, ""), // 44 + new HeaderField(HeaderNames.LINK, ""), // 45 + new HeaderField(HeaderNames.LOCATION, ""), // 46 + new HeaderField(HeaderNames.MAX_FORWARDS, ""), // 47 + new HeaderField(HeaderNames.PROXY_AUTHENTICATE, ""), // 48 + new HeaderField(HeaderNames.PROXY_AUTHORIZATION, ""), // 49 + new HeaderField(HeaderNames.RANGE, ""), // 50 + new HeaderField(HeaderNames.REFERER, ""), // 51 + new HeaderField(HeaderNames.REFRESH, ""), // 52 + new HeaderField(HeaderNames.RETRY_AFTER, ""), // 53 + new HeaderField(HeaderNames.SERVER, ""), // 54 + new HeaderField(HeaderNames.SET_COOKIE, ""), // 55 + new HeaderField(HeaderNames.STRICT_TRANSPORT_SECURITY, ""), // 56 + new HeaderField(HeaderNames.TRANSFER_ENCODING, ""), // 57 + new HeaderField(HeaderNames.USER_AGENT, ""), // 58 + new HeaderField(HeaderNames.VARY, ""), // 59 + new HeaderField(HeaderNames.VIA, ""), // 60 + new HeaderField(HeaderNames.WWW_AUTHENTICATE, "") // 61 }; /** @@ -113,7 +119,7 @@ private StaticTable() {} // Find max name length int maxLen = 0; for (int i = 1; i <= SIZE; i++) { - int len = ENTRIES[i][0].length(); + int len = ENTRIES[i].name().length(); if (len > maxLen) { maxLen = len; } @@ -123,7 +129,7 @@ private StaticTable() {} // First pass: count entries per length int[] counts = new int[MAX_NAME_LEN + 1]; for (int i = 1; i <= SIZE; i++) { - counts[ENTRIES[i][0].length()]++; + counts[ENTRIES[i].name().length()]++; } // Allocate buckets (empty bucket for lengths with no entries) @@ -135,7 +141,7 @@ private StaticTable() {} // Second pass: fill buckets int[] pos = new int[MAX_NAME_LEN + 1]; for (int i = 1; i <= SIZE; i++) { - int len = ENTRIES[i][0].length(); + int len = ENTRIES[i].name().length(); buckets[len][pos[len]++] = i; } @@ -143,31 +149,13 @@ private StaticTable() {} } /** - * Get the header name at the given index. + * Get the header field at the given index. * * @param index 1-based index into static table - * @return header name - * @throws IndexOutOfBoundsException if index is out of range + * @return header field */ - static String getName(int index) { - if (index < 1 || index > SIZE) { - throw new IndexOutOfBoundsException("Static table index out of range: " + index); - } - return ENTRIES[index][0]; - } - - /** - * Get the header value at the given index. - * - * @param index 1-based index into static table - * @return header value (may be empty string) - * @throws IndexOutOfBoundsException if index is out of range - */ - static String getValue(int index) { - if (index < 1 || index > SIZE) { - throw new IndexOutOfBoundsException("Static table index out of range: " + index); - } - return ENTRIES[index][1]; + static HeaderField get(int index) { + return ENTRIES[index]; } /** @@ -183,8 +171,9 @@ static int findFullMatch(String name, String value) { return -1; } for (int idx : NAME_BUCKETS_BY_LEN[len]) { - String[] e = ENTRIES[idx]; - if (e[0].equals(name) && e[1].equals(value)) { + HeaderField e = ENTRIES[idx]; + var entryName = e.name(); + if ((entryName == name || entryName.equals(name)) && e.value().equals(value)) { return idx; } } @@ -201,7 +190,9 @@ static int findNameMatch(String name) { int len = name.length(); if (len <= MAX_NAME_LEN) { for (int idx : NAME_BUCKETS_BY_LEN[len]) { - if (ENTRIES[idx][0].equals(name)) { + var entry = ENTRIES[idx]; + var entryName = entry.name(); + if (entryName == name || entryName.equals(name)) { return idx; } } diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManagerTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManagerTest.java index 4989cf5a7..f9cc9871b 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManagerTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManagerTest.java @@ -31,7 +31,7 @@ class H1ConnectionManagerTest { void tryAcquireReturnsNullWhenPoolEmpty() { var manager = new H1ConnectionManager(MAX_IDLE_NANOS); - var result = manager.tryAcquire(TEST_ROUTE, ignored -> new H1ConnectionManager.HostPool(10)); + var result = manager.tryAcquire(TEST_ROUTE, 10); assertNull(result, "Should return null when pool is empty"); } @@ -46,7 +46,7 @@ void tryAcquireReturnsPooledConnection() { manager.ensurePool(TEST_ROUTE, 10); manager.release(TEST_ROUTE, connection, false); - var result = manager.tryAcquire(TEST_ROUTE, ignored -> new H1ConnectionManager.HostPool(10)); + var result = manager.tryAcquire(TEST_ROUTE, 10); assertNotNull(result, "Should return pooled connection"); assertEquals(connection, result.connection(), "Should return the same connection"); @@ -68,7 +68,7 @@ public void close() { Thread.sleep(50); // Wait longer than max idle time - var result = manager.tryAcquire(TEST_ROUTE, ignored -> new H1ConnectionManager.HostPool(10)); + var result = manager.tryAcquire(TEST_ROUTE, 10); assertNull(result, "Should not return overly idle connection"); assertTrue(closeCalled.get(), "Overly idle connection should be closed"); @@ -91,7 +91,7 @@ public boolean validateForReuse() { Thread.sleep(1100); // Wait > 1 second (VALIDATION_THRESHOLD_NANOS) - var result = manager.tryAcquire(TEST_ROUTE, ignored -> new H1ConnectionManager.HostPool(10)); + var result = manager.tryAcquire(TEST_ROUTE, 10); assertNotNull(result, "Should return validated connection"); assertTrue(validateCalled.get(), "validateForReuse should be called for connections idle > 1s"); @@ -113,7 +113,7 @@ public boolean isActive() { manager.release(TEST_ROUTE, invalidConnection, false); // Should skip invalid and return valid (LIFO order, so invalid is tried first) - var result = manager.tryAcquire(TEST_ROUTE, ignored -> new H1ConnectionManager.HostPool(10)); + var result = manager.tryAcquire(TEST_ROUTE, 10); assertNotNull(result, "Should return valid connection"); assertEquals(validConnection, result.connection(), "Should skip invalid and return valid"); @@ -141,7 +141,7 @@ public void close() { // Connection becomes inactive after being pooled active.set(false); - manager.tryAcquire(TEST_ROUTE, ignored -> new H1ConnectionManager.HostPool(10)); + manager.tryAcquire(TEST_ROUTE, 10); assertTrue(closeCalled.get(), "Invalid connection should be closed"); } @@ -192,7 +192,7 @@ void removeRemovesConnectionFromPool() { manager.release(TEST_ROUTE, connection, false); manager.remove(TEST_ROUTE, connection); - var result = manager.tryAcquire(TEST_ROUTE, ignored -> new H1ConnectionManager.HostPool(10)); + var result = manager.tryAcquire(TEST_ROUTE, 10); assertNull(result, "Connection should be removed from pool"); } diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/H1UtilsTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/H1UtilsTest.java new file mode 100644 index 000000000..fabbe577a --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/H1UtilsTest.java @@ -0,0 +1,122 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.smithy.java.http.api.HeaderNames; +import software.amazon.smithy.java.http.api.HttpHeaders; + +class H1UtilsTest { + + static Stream knownHeaders() { + return Stream.of( + // Common headers + Arguments.of("date", HeaderNames.DATE), + Arguments.of("vary", HeaderNames.VARY), + Arguments.of("etag", HeaderNames.ETAG), + Arguments.of("server", HeaderNames.SERVER), + Arguments.of("trailer", HeaderNames.TRAILER), + Arguments.of("expires", HeaderNames.EXPIRES), + Arguments.of("upgrade", HeaderNames.UPGRADE), + Arguments.of("location", HeaderNames.LOCATION), + Arguments.of("connection", HeaderNames.CONNECTION), + Arguments.of("keep-alive", HeaderNames.KEEP_ALIVE), + Arguments.of("set-cookie", HeaderNames.SET_COOKIE), + Arguments.of("content-type", HeaderNames.CONTENT_TYPE), + Arguments.of("cache-control", HeaderNames.CACHE_CONTROL), + Arguments.of("last-modified", HeaderNames.LAST_MODIFIED), + Arguments.of("content-range", HeaderNames.CONTENT_RANGE), + Arguments.of("accept-ranges", HeaderNames.ACCEPT_RANGES), + Arguments.of("content-length", HeaderNames.CONTENT_LENGTH), + Arguments.of("content-encoding", HeaderNames.CONTENT_ENCODING), + Arguments.of("x-amzn-requestid", HeaderNames.X_AMZN_REQUESTID), + Arguments.of("x-amz-request-id", HeaderNames.X_AMZ_REQUEST_ID), + Arguments.of("www-authenticate", HeaderNames.WWW_AUTHENTICATE), + Arguments.of("proxy-connection", HeaderNames.PROXY_CONNECTION), + Arguments.of("transfer-encoding", HeaderNames.TRANSFER_ENCODING), + Arguments.of("proxy-authenticate", HeaderNames.PROXY_AUTHENTICATE)); + } + + @ParameterizedTest + @MethodSource("knownHeaders") + void internsKnownHeader(String header, String expected) { + byte[] buf = header.getBytes(StandardCharsets.US_ASCII); + String result = HeaderNames.canonicalize(buf, 0, buf.length); + + assertSame(expected, result); + } + + @ParameterizedTest + @MethodSource("knownHeaders") + void internsKnownHeaderCaseInsensitive(String header, String expected) { + byte[] buf = header.toUpperCase().getBytes(StandardCharsets.US_ASCII); + String result = HeaderNames.canonicalize(buf, 0, buf.length); + + assertSame(expected, result); + } + + @Test + void returnsNewStringForUnknownHeader() { + byte[] buf = "x-custom".getBytes(StandardCharsets.US_ASCII); + String result = HeaderNames.canonicalize(buf, 0, buf.length); + + assertEquals("x-custom", result); + } + + @Test + void returnsNewStringForUnknownLengthMatch() { + // Same length as "date" but different content + byte[] buf = "test".getBytes(StandardCharsets.US_ASCII); + String result = HeaderNames.canonicalize(buf, 0, buf.length); + + assertEquals("test", result); + } + + @Test + void parseHeaderLineReturnsNullForMissingColon() { + byte[] buf = "invalid header line".getBytes(StandardCharsets.US_ASCII); + var headers = HttpHeaders.ofModifiable(); + String result = H1Utils.parseHeaderLine(buf, buf.length, headers); + + assertNull(result); + } + + @Test + void parseHeaderLineReturnsNullForColonAtStart() { + byte[] buf = ": value".getBytes(StandardCharsets.US_ASCII); + var headers = HttpHeaders.ofModifiable(); + String result = H1Utils.parseHeaderLine(buf, buf.length, headers); + + assertNull(result); + } + + @Test + void parseHeaderLineTrimsWhitespace() { + byte[] buf = "name: value ".getBytes(StandardCharsets.US_ASCII); + var headers = HttpHeaders.ofModifiable(); + H1Utils.parseHeaderLine(buf, buf.length, headers); + + assertEquals("value", headers.firstValue("name")); + } + + @Test + void parseHeaderLineTrimsTab() { + byte[] buf = "name:\t\tvalue\t".getBytes(StandardCharsets.US_ASCII); + var headers = HttpHeaders.ofModifiable(); + H1Utils.parseHeaderLine(buf, buf.length, headers); + + assertEquals("value", headers.firstValue("name")); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/HttpUtilsTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/HttpUtilsTest.java deleted file mode 100644 index 199195025..000000000 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/HttpUtilsTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.h1; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; - -import java.nio.charset.StandardCharsets; -import java.util.stream.Stream; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import software.amazon.smithy.java.http.api.HttpHeaders; - -class HttpUtilsTest { - - static Stream knownHeaders() { - return Stream.of( - // GROUP_4 - Arguments.of("date"), - Arguments.of("vary"), - Arguments.of("etag"), - // GROUP_6 - Arguments.of("server"), - // GROUP_7 - Arguments.of("trailer"), - Arguments.of("expires"), - Arguments.of("upgrade"), - // GROUP_8 - Arguments.of("location"), - // GROUP_10 - Arguments.of("connection"), - Arguments.of("keep-alive"), - Arguments.of("set-cookie"), - // GROUP_12 - Arguments.of("content-type"), - // GROUP_13 - Arguments.of("cache-control"), - Arguments.of("last-modified"), - Arguments.of("content-range"), - Arguments.of("accept-ranges"), - // GROUP_14 - Arguments.of("content-length"), - // GROUP_16 - Arguments.of("content-encoding"), - Arguments.of("x-amzn-requestid"), - Arguments.of("x-amz-request-id"), - Arguments.of("www-authenticate"), - Arguments.of("proxy-connection"), - // GROUP_17 - Arguments.of("transfer-encoding"), - // GROUP_18 - Arguments.of("proxy-authenticate")); - } - - @ParameterizedTest - @MethodSource("knownHeaders") - void internsKnownHeader(String header) { - byte[] buf = header.getBytes(StandardCharsets.US_ASCII); - String result = HttpUtils.internHeader(buf, 0, buf.length); - - assertSame(header, result); - } - - @ParameterizedTest - @MethodSource("knownHeaders") - void internsKnownHeaderCaseInsensitive(String header) { - byte[] buf = header.toUpperCase().getBytes(StandardCharsets.US_ASCII); - String result = HttpUtils.internHeader(buf, 0, buf.length); - - assertSame(header, result); - } - - @Test - void returnsNewStringForUnknownHeader() { - byte[] buf = "x-custom".getBytes(StandardCharsets.US_ASCII); - String result = HttpUtils.internHeader(buf, 0, buf.length); - - assertEquals("x-custom", result); - } - - @Test - void returnsNewStringForUnknownLengthMatch() { - // Same length as "date" but different content - byte[] buf = "test".getBytes(StandardCharsets.US_ASCII); - String result = HttpUtils.internHeader(buf, 0, buf.length); - - assertEquals("test", result); - } - - @Test - void parseHeaderLineReturnsNullForMissingColon() { - byte[] buf = "invalid header line".getBytes(StandardCharsets.US_ASCII); - var headers = HttpHeaders.ofModifiable(); - String result = HttpUtils.parseHeaderLine(buf, buf.length, headers); - - assertNull(result); - } - - @Test - void parseHeaderLineReturnsNullForColonAtStart() { - byte[] buf = ": value".getBytes(StandardCharsets.US_ASCII); - var headers = HttpHeaders.ofModifiable(); - String result = HttpUtils.parseHeaderLine(buf, buf.length, headers); - - assertNull(result); - } - - @Test - void parseHeaderLineTrimsWhitespace() { - byte[] buf = "name: value ".getBytes(StandardCharsets.US_ASCII); - var headers = HttpHeaders.ofModifiable(); - HttpUtils.parseHeaderLine(buf, buf.length, headers); - - assertEquals("value", headers.firstValue("name")); - } - - @Test - void parseHeaderLineTrimsTab() { - byte[] buf = "name:\t\tvalue\t".getBytes(StandardCharsets.US_ASCII); - var headers = HttpHeaders.ofModifiable(); - HttpUtils.parseHeaderLine(buf, buf.length, headers); - - assertEquals("value", headers.firstValue("name")); - } -} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/BufferPoolTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/BufferPoolTest.java new file mode 100644 index 000000000..d359c3782 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/BufferPoolTest.java @@ -0,0 +1,233 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.client.BufferPool; + +class BufferPoolTest { + + @Test + void borrowReturnsBufferOfRequestedSize() { + BufferPool pool = new BufferPool(10, 1024, 1024, 128); + + byte[] buffer = pool.borrow(256); + + assertNotNull(buffer); + assertTrue(buffer.length >= 256); + } + + @Test + void borrowReturnsDefaultSizeWhenRequestedSizeIsSmaller() { + BufferPool pool = new BufferPool(10, 1024, 1024, 128); + + byte[] buffer = pool.borrow(64); + + assertNotNull(buffer); + assertEquals(128, buffer.length); // Default size + } + + @Test + void releasedBufferCanBeReused() { + BufferPool pool = new BufferPool(10, 1024, 1024, 128); + + byte[] buffer1 = pool.borrow(128); + pool.release(buffer1); + byte[] buffer2 = pool.borrow(128); + + assertSame(buffer1, buffer2, "Should reuse the same buffer"); + } + + @Test + void poolSizeIncreasesOnRelease() { + BufferPool pool = new BufferPool(10, 1024, 1024, 128); + assertEquals(0, pool.size()); + + byte[] buffer = pool.borrow(128); + pool.release(buffer); + + assertEquals(1, pool.size()); + } + + @Test + void poolSizeDecreasesOnBorrow() { + BufferPool pool = new BufferPool(10, 1024, 1024, 128); + + byte[] buffer1 = pool.borrow(128); + pool.release(buffer1); + assertEquals(1, pool.size()); + + pool.borrow(128); + assertEquals(0, pool.size()); + } + + @Test + void poolRespectsMaxSize() { + BufferPool pool = new BufferPool(2, 1024, 1024, 128); + + // Fill pool to max + pool.release(new byte[128]); + pool.release(new byte[128]); + assertEquals(2, pool.size()); + + // Try to add one more - should be discarded + pool.release(new byte[128]); + assertEquals(2, pool.size()); + } + + @Test + void buffersLargerThanMaxPoolableSizeAreNotPooled() { + BufferPool pool = new BufferPool(10, 1024, 256, 128); + + byte[] largeBuffer = new byte[512]; // Larger than maxPoolableSize (256) + pool.release(largeBuffer); + + assertEquals(0, pool.size(), "Buffer larger than maxPoolableSize should not be pooled"); + } + + @Test + void borrowThrowsWhenRequestedSizeExceedsMaxBufferSize() { + BufferPool pool = new BufferPool(10, 256, 256, 128); + + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + () -> pool.borrow(512) // Larger than maxBufferSize (256) + ); + + assertTrue(ex.getMessage().contains("512")); + assertTrue(ex.getMessage().contains("256")); + } + + @Test + void nullBufferIsIgnored() { + BufferPool pool = new BufferPool(10, 1024, 1024, 128); + + pool.release(null); // Should not throw + + assertEquals(0, pool.size()); + } + + @Test + void clearRemovesAllBuffers() { + BufferPool pool = new BufferPool(10, 1024, 1024, 128); + + pool.release(new byte[128]); + pool.release(new byte[128]); + pool.release(new byte[128]); + assertEquals(3, pool.size()); + + pool.clear(); + + assertEquals(0, pool.size()); + } + + @Test + void tooSmallPooledBufferIsDropped() { + BufferPool pool = new BufferPool(10, 1024, 1024, 128); + + // Release a small buffer + byte[] smallBuffer = new byte[64]; + pool.release(smallBuffer); + assertEquals(1, pool.size()); + + // Borrow a larger buffer - small one is dropped (best-effort, no re-pooling) + byte[] buffer = pool.borrow(256); + assertEquals(0, pool.size()); // Small buffer was dropped + assertTrue(buffer.length >= 256); + assertNotSame(smallBuffer, buffer); + } + + @Test + void constructorValidatesMaxPoolCount() { + assertThrows(IllegalArgumentException.class, () -> new BufferPool(0, 1024, 1024, 128)); + assertThrows(IllegalArgumentException.class, () -> new BufferPool(-1, 1024, 1024, 128)); + } + + @Test + void constructorValidatesDefaultBufferSize() { + assertThrows(IllegalArgumentException.class, () -> new BufferPool(10, 1024, 1024, 0)); + assertThrows(IllegalArgumentException.class, () -> new BufferPool(10, 1024, 1024, -1)); + } + + @Test + void constructorValidatesMaxPoolableSize() { + // maxPoolableSize must be > 0 + assertThrows(IllegalArgumentException.class, () -> new BufferPool(10, 1024, 0, 128)); + // maxPoolableSize must be <= maxBufferSize + assertThrows(IllegalArgumentException.class, () -> new BufferPool(10, 256, 512, 128)); + } + + @Test + void borrowThrowsWhenMinSizeIsZeroOrNegative() { + BufferPool pool = new BufferPool(10, 1024, 1024, 128); + + assertThrows(IllegalArgumentException.class, () -> pool.borrow(0)); + assertThrows(IllegalArgumentException.class, () -> pool.borrow(-1)); + } + + @Test + void lifoOrderPreserved() { + BufferPool pool = new BufferPool(10, 1024, 1024, 128); + + byte[] buffer1 = new byte[128]; + byte[] buffer2 = new byte[128]; + byte[] buffer3 = new byte[128]; + + pool.release(buffer1); + pool.release(buffer2); + pool.release(buffer3); + + // LIFO: should get buffer3, buffer2, buffer1 back + assertSame(buffer3, pool.borrow(128)); + assertSame(buffer2, pool.borrow(128)); + assertSame(buffer1, pool.borrow(128)); + } + + @Test + void concurrentBorrowAndReleaseIsThreadSafe() throws InterruptedException { + BufferPool pool = new BufferPool(100, 1024, 1024, 128); + int threadCount = 10; + int operationsPerThread = 1000; + + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + CountDownLatch latch = new CountDownLatch(threadCount); + List errors = new ArrayList<>(); + + for (int t = 0; t < threadCount; t++) { + executor.submit(() -> { + try { + for (int i = 0; i < operationsPerThread; i++) { + byte[] buffer = pool.borrow(128); + assertNotNull(buffer); + // Simulate some work + buffer[0] = (byte) i; + pool.release(buffer); + } + } catch (Throwable e) { + synchronized (errors) { + errors.add(e); + } + } finally { + latch.countDown(); + } + }); + } + + assertTrue(latch.await(10, TimeUnit.SECONDS)); + executor.shutdown(); + + assertTrue(errors.isEmpty(), "Concurrent operations should not throw: " + errors); + assertTrue(pool.size() <= 100, "Pool size should not exceed max"); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoderTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoderTest.java index d8b6f639d..00bb6ce64 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoderTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoderTest.java @@ -31,7 +31,7 @@ void decodesIndexedNameFromDynamicTable() throws IOException { var out2 = new ByteArrayOutputStream(); encoder.beginHeaderBlock(out2); encoder.encodeHeader(out2, "x-custom-name", "value2", false); - List headers = decoder.decode(out2.toByteArray()); + List headers = decoder.decode(out2.toByteArray()); assertEquals(1, headers.size()); assertEquals("x-custom-name", headers.getFirst().name()); @@ -100,7 +100,7 @@ void allowsTableSizeUpdateAtBeginning() throws IOException { // Actually simpler: 0x20 = table size 0 (just 0x20 with 5-bit prefix) byte[] valid = {0x20, (byte) 0x82}; // table size 0, then :method GET var decoder = new HpackDecoder(4096); - List headers = decoder.decode(valid); + List headers = decoder.decode(valid); assertEquals(1, headers.size()); assertEquals(":method", headers.getFirst().name()); @@ -116,7 +116,7 @@ void decodesLiteralNeverIndexed() throws IOException { // "value" byte[] data = {0x10, 0x04, 't', 'e', 's', 't', 0x05, 'v', 'a', 'l', 'u', 'e'}; var decoder = new HpackDecoder(4096); - List headers = decoder.decode(data); + List headers = decoder.decode(data); assertEquals(1, headers.size()); assertEquals("test", headers.getFirst().name()); @@ -128,7 +128,7 @@ void decodesLiteralWithoutIndexing() throws IOException { // 0x00 = literal without indexing, name index 0 byte[] data = {0x00, 0x04, 't', 'e', 's', 't', 0x05, 'v', 'a', 'l', 'u', 'e'}; var decoder = new HpackDecoder(4096); - List headers = decoder.decode(data); + List headers = decoder.decode(data); assertEquals(1, headers.size()); assertEquals("test", headers.getFirst().name()); diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoderTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoderTest.java index be7bae901..1fabd1c8f 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoderTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoderTest.java @@ -23,7 +23,7 @@ void encodesStaticIndexedHeader() throws IOException { encoder.encodeHeader(out, ":method", "GET", false); var decoder = new HpackDecoder(4096); - List headers = decoder.decode(out.toByteArray()); + List headers = decoder.decode(out.toByteArray()); assertEquals(1, headers.size()); assertEquals(":method", headers.getFirst().name()); @@ -38,7 +38,7 @@ void encodesLiteralWithIndexing() throws IOException { encoder.encodeHeader(out, "x-custom", "value", false); var decoder = new HpackDecoder(4096); - List headers = decoder.decode(out.toByteArray()); + List headers = decoder.decode(out.toByteArray()); assertEquals(1, headers.size()); assertEquals("x-custom", headers.getFirst().name()); @@ -55,7 +55,7 @@ void encodesMultipleHeaders() throws IOException { encoder.encodeHeader(out, ":scheme", "https", false); var decoder = new HpackDecoder(4096); - List headers = decoder.decode(out.toByteArray()); + List headers = decoder.decode(out.toByteArray()); assertEquals(3, headers.size()); assertEquals(":method", headers.get(0).name()); @@ -82,7 +82,7 @@ void reusesDynamicTableEntry() throws IOException { var out2 = new ByteArrayOutputStream(); encoder.beginHeaderBlock(out2); encoder.encodeHeader(out2, "x-custom", "value", false); - List headers = decoder.decode(out2.toByteArray()); + List headers = decoder.decode(out2.toByteArray()); int secondSize = out2.size(); assertEquals(1, headers.size()); @@ -107,7 +107,7 @@ void encodesSensitiveHeaderNeverIndexed() throws IOException { var out2 = new ByteArrayOutputStream(); encoder.beginHeaderBlock(out2); encoder.encodeHeader(out2, "x-secret", "password", true); - List headers = decoder.decode(out2.toByteArray()); + List headers = decoder.decode(out2.toByteArray()); assertEquals(1, headers.size()); assertEquals("x-secret", headers.getFirst().name()); @@ -130,7 +130,7 @@ void authorizationHeaderNeverIndexed() throws IOException { var out2 = new ByteArrayOutputStream(); encoder.beginHeaderBlock(out2); encoder.encodeHeader(out2, "authorization", "Bearer token", false); - List headers = decoder.decode(out2.toByteArray()); + List headers = decoder.decode(out2.toByteArray()); assertEquals(1, headers.size()); assertEquals("authorization", headers.getFirst().name()); @@ -146,7 +146,7 @@ void encodesWithoutHuffman() throws IOException { encoder.encodeHeader(out, "x-test", "hello", false); var decoder = new HpackDecoder(4096); - List headers = decoder.decode(out.toByteArray()); + List headers = decoder.decode(out.toByteArray()); assertEquals(1, headers.size()); assertEquals("x-test", headers.getFirst().name()); @@ -166,7 +166,7 @@ void emitsTableSizeUpdate() throws IOException { encoder.encodeHeader(out, ":method", "GET", false); // Decoder should handle the table size update - List headers = decoder.decode(out.toByteArray()); + List headers = decoder.decode(out.toByteArray()); assertEquals(1, headers.size()); assertEquals(":method", headers.getFirst().name()); @@ -207,7 +207,7 @@ void encodesLargeInteger() throws IOException { encoder.encodeHeader(out, "x-long", longValue, false); var decoder = new HpackDecoder(4096); - List headers = decoder.decode(out.toByteArray()); + List headers = decoder.decode(out.toByteArray()); assertEquals(1, headers.size()); assertEquals("x-long", headers.getFirst().name()); @@ -223,7 +223,7 @@ void encodesStaticNameWithNewValue() throws IOException { encoder.encodeHeader(out, ":path", "/custom/path", false); var decoder = new HpackDecoder(4096); - List headers = decoder.decode(out.toByteArray()); + List headers = decoder.decode(out.toByteArray()); assertEquals(1, headers.size()); assertEquals(":path", headers.getFirst().name()); diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackTestSuiteTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackTestSuiteTest.java index b1b45463b..ef72cbeee 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackTestSuiteTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackTestSuiteTest.java @@ -81,7 +81,7 @@ void decodeTestCase(String filename, int seqno, String wireHex, List e // Decode this case's wire bytes byte[] wireBytes = hexToBytes(wireHex); - List result = decoder.decode(wireBytes); + List result = decoder.decode(wireBytes); // Verify the decoded headers match expected assertEquals(expectedHeaders.size(), @@ -90,7 +90,7 @@ void decodeTestCase(String filename, int seqno, String wireHex, List e for (int i = 0; i < expectedHeaders.size(); i++) { String[] expected = expectedHeaders.get(i); - HpackDecoder.HeaderField actual = result.get(i); + HeaderField actual = result.get(i); assertEquals(expected[0], actual.name(), From d946e982fe7a82efe0246669c6855c3d523b8432 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Mon, 8 Dec 2025 12:38:37 -0600 Subject: [PATCH 24/60] Make h2StreamsPerConnection configurable --- .../client/VirtualThreadScalingBenchmark.java | 3 +- .../client/connection/HttpConnectionPool.java | 6 +-- .../connection/HttpConnectionPoolBuilder.java | 37 +++++++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java index 0120ad361..b99692d15 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java @@ -215,6 +215,7 @@ public void setup() throws Exception { .maxIdleTime(Duration.ofMinutes(2)) .dnsResolver(staticDns) .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) + .h2StreamsPerConnection(100) .build()) .build(); @@ -580,7 +581,7 @@ public void nettyH2c(RequestCounter counter) throws InterruptedException { @Benchmark @Threads(1) public void nettyH2cPooled(RequestCounter counter) throws InterruptedException { - runNettyH2c(20, counter); + runNettyH2c(3, counter); } private void runNettyH2c(int numConnections, RequestCounter counter) throws InterruptedException { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java index 60a43f696..629857d89 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java @@ -127,10 +127,6 @@ * @see HttpVersionPolicy */ public final class HttpConnectionPool implements ConnectionPool { - // Soft limit on streams per connection before creating a new one. - // Server's MAX_CONCURRENT_STREAMS is the hard limit; this spreads load before hitting it. - private static final int STREAMS_PER_CONNECTION = 1024; - private final int defaultMaxConnectionsPerRoute; private final Map perHostLimits; private final int maxTotalConnections; @@ -188,7 +184,7 @@ public final class HttpConnectionPool implements ConnectionPool { this.h1Manager = new H1ConnectionManager(this.maxIdleTimeNanos); this.connectionPermits = new Semaphore(builder.maxTotalConnections, false); this.listeners = List.copyOf(builder.listeners); - this.h2Manager = new H2ConnectionManager(STREAMS_PER_CONNECTION, listeners, this::onNewH2Connection); + this.h2Manager = new H2ConnectionManager(builder.h2StreamsPerConnection, listeners, this::onNewH2Connection); this.cleanupThread = Thread.ofVirtual().name("http-pool-cleanup").start(this::cleanupIdleConnections); } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java index fd82e01a5..d70e62d05 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java @@ -35,6 +35,7 @@ public final class HttpConnectionPoolBuilder { HttpVersionPolicy versionPolicy = HttpVersionPolicy.AUTOMATIC; DnsResolver dnsResolver; HttpSocketFactory socketFactory = HttpSocketFactory::defaultSocketFactory; + int h2StreamsPerConnection = 100; final List listeners = new LinkedList<>(); /** @@ -367,6 +368,42 @@ public HttpConnectionPoolBuilder socketFactory(HttpSocketFactory socketFactory) return this; } + /** + * Set maximum concurrent streams per HTTP/2 connection before creating a new connection (default: 100). + * + *

      This is a soft limit that controls when the pool creates additional HTTP/2 connections + * to spread load. When an existing connection reaches this many active streams, the pool + * will create a new connection for the next request (subject to {@link #maxTotalConnections(int)}). + * + *

      This is distinct from the server's {@code SETTINGS_MAX_CONCURRENT_STREAMS}, which is + * a hard limit enforced by the server. This client-side limit helps balance load across + * multiple connections to reduce lock contention and improve throughput under high concurrency. + * + *

      RFC 7540 Section 6.5.2 + * recommends servers set {@code SETTINGS_MAX_CONCURRENT_STREAMS} to at least 100 to avoid + * unnecessarily limiting parallelism. This default aligns with that recommendation and matches + * Go's net/http + * default of 100. + * + *

      Performance considerations: Lower values create more connections but reduce + * per-connection lock contention. Higher values use fewer connections but may increase + * contention under high concurrency. + * + *

      Note: This setting only applies to HTTP/2 connections. HTTP/1.1 connections + * handle one request at a time and are managed by {@link #maxConnectionsPerRoute(int)}. + * + * @param streams maximum streams per connection, must be positive + * @return this builder + * @throws IllegalArgumentException if streams is not positive + */ + public HttpConnectionPoolBuilder h2StreamsPerConnection(int streams) { + if (streams <= 0) { + throw new IllegalArgumentException("h2StreamsPerConnection must be positive: " + streams); + } + this.h2StreamsPerConnection = streams; + return this; + } + /** * Add a listener for connection pool lifecycle events. * From b34631738383f7d5bfb3506e01f71b3d66541731 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Mon, 8 Dec 2025 14:18:16 -0600 Subject: [PATCH 25/60] Block to acquire connection, clarify soft limits --- .../connection/H2ConnectionManager.java | 84 ++++++++++++------- .../client/connection/HttpConnectionPool.java | 5 +- .../connection/HttpConnectionPoolBuilder.java | 38 ++++++--- 3 files changed, 86 insertions(+), 41 deletions(-) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java index 48249cc8f..d786397b3 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java @@ -31,6 +31,7 @@ private static final class RouteState { private final ConcurrentHashMap routes = new ConcurrentHashMap<>(); private final int streamsPerConnection; + private final long acquireTimeoutMs; private final List listeners; private final ConnectionFactory connectionFactory; @@ -41,10 +42,12 @@ interface ConnectionFactory { H2ConnectionManager( int streamsPerConnection, + long acquireTimeoutMs, List listeners, ConnectionFactory connectionFactory ) { this.streamsPerConnection = streamsPerConnection; + this.acquireTimeoutMs = acquireTimeoutMs; this.listeners = listeners; this.connectionFactory = connectionFactory; } @@ -61,7 +64,7 @@ private RouteState stateFor(Route route) { * which blocks waiting for a permit. Thread B tries to release a permit but first * needs to unregister, which requires the RouteState lock held by A. */ - H2Connection acquire(Route route) throws IOException { + H2Connection acquire(Route route, int maxConnectionsForRoute) throws IOException { RouteState state = stateFor(route); // Fast path: snapshot of current connections, no locking @@ -71,16 +74,52 @@ H2Connection acquire(Route route) throws IOException { return conn; } - // Slow path: check existing under lock, but DON'T create while holding lock + long deadline = System.currentTimeMillis() + acquireTimeoutMs; + + // Slow path: check existing under lock, wait if at limit synchronized (state) { - H2Connection[] snapshot = state.conns; - H2Connection rechecked = tryAcquireUnderLimit(snapshot); - if (rechecked != null) { - notifyAcquire(rechecked, true); - return rechecked; + while (true) { + // Try to find a connection under the soft limit (streamsPerConnection) + H2Connection[] snapshot = state.conns; + H2Connection rechecked = tryAcquireUnderLimit(snapshot); + if (rechecked != null) { + notifyAcquire(rechecked, true); + return rechecked; + } else if (snapshot.length < maxConnectionsForRoute) { + // Under connection limit: exit loop to create new connection + break; + } + + // At connection limit, so try to reuse any connection even if over soft limit + // (but still under server's SETTINGS_MAX_CONCURRENT_STREAMS hard limit) + H2Connection overLimit = tryAcquire(snapshot); + if (overLimit != null) { + notifyAcquire(overLimit, true); + return overLimit; + } + + // No connections available, so wait for one to free up capacity. + long remaining = deadline - System.currentTimeMillis(); + if (remaining <= 0) { + throw new IOException("Acquire timeout: no connection available after " + + acquireTimeoutMs + "ms for " + route); + } + + try { + // Wait and then retry the loop. + state.wait(remaining); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Interrupted while waiting for connection", e); + } } } + return createNewH2Connection(route, state); + } + + @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") + private H2Connection createNewH2Connection(Route route, RouteState state) throws IOException { // Create new connection OUTSIDE the lock to avoid deadlock. Note: Multiple threads may race to create // connections for the same route. This is acceptable: we'd rather over-create slightly than deadlock. H2Connection newConn = connectionFactory.create(route); @@ -159,20 +198,6 @@ private H2Connection tryAcquireUnderLimit(H2Connection[] conns) { return null; } - /** - * Register a new connection for the route. - */ - void register(Route route, H2Connection conn) { - RouteState state = stateFor(route); - synchronized (state) { - H2Connection[] cur = state.conns; - H2Connection[] next = new H2Connection[cur.length + 1]; - System.arraycopy(cur, 0, next, 0, cur.length); - next[cur.length] = conn; - state.conns = next; - } - } - /** * Unregister a connection from the route. */ @@ -191,17 +216,19 @@ void unregister(Route route, H2Connection conn) { break; } } + if (idx < 0) { return; - } - if (n == 1) { + } else if (n == 1) { state.conns = EMPTY; - return; + } else { + // Compact array: copy elements before and after removed connection + H2Connection[] next = new H2Connection[n - 1]; + System.arraycopy(cur, 0, next, 0, idx); + System.arraycopy(cur, idx + 1, next, idx, n - idx - 1); + state.conns = next; } - H2Connection[] next = new H2Connection[n - 1]; - System.arraycopy(cur, 0, next, 0, idx); - System.arraycopy(cur, idx + 1, next, idx, n - idx - 1); - state.conns = next; + state.notifyAll(); // Wake threads waiting for capacity } } @@ -272,6 +299,7 @@ void cleanupAllDead(BiConsumer onRemove) { * @param onRemove callback for removed connections * @return number of connections removed */ + @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") int cleanupIdle(long maxIdleTimeNanos, BiConsumer onRemove) { int removed = 0; for (RouteState state : routes.values()) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java index 629857d89..d67449b5b 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java @@ -184,7 +184,7 @@ public final class HttpConnectionPool implements ConnectionPool { this.h1Manager = new H1ConnectionManager(this.maxIdleTimeNanos); this.connectionPermits = new Semaphore(builder.maxTotalConnections, false); this.listeners = List.copyOf(builder.listeners); - this.h2Manager = new H2ConnectionManager(builder.h2StreamsPerConnection, listeners, this::onNewH2Connection); + this.h2Manager = new H2ConnectionManager(builder.h2StreamsPerConnection, this.acquireTimeoutMs, listeners, this::onNewH2Connection); this.cleanupThread = Thread.ofVirtual().name("http-pool-cleanup").start(this::cleanupIdleConnections); } @@ -203,7 +203,8 @@ public HttpConnection acquire(Route route) throws IOException { throw new IllegalStateException("Connection pool is closed"); } else if ((route.isSecure() && versionPolicy != HttpVersionPolicy.ENFORCE_HTTP_1_1) || (!route.isSecure() && versionPolicy.usesH2cForCleartext())) { - return h2Manager.acquire(route); + int maxConns = getMaxConnectionsForRoute(route); + return h2Manager.acquire(route, maxConns); } else { return acquireH1(route); } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java index d70e62d05..4cee9c220 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java @@ -21,9 +21,11 @@ * Builder for HttpConnectionPool. */ public final class HttpConnectionPoolBuilder { + int maxTotalConnections = 256; int maxConnectionsPerRoute = 20; + int h2StreamsPerConnection = 100; final Map perHostLimits = new HashMap<>(); - int maxTotalConnections = 200; + Duration maxIdleTime = Duration.ofMinutes(2); Duration acquireTimeout = Duration.ofSeconds(30); Duration connectTimeout = Duration.ofSeconds(10); @@ -35,7 +37,6 @@ public final class HttpConnectionPoolBuilder { HttpVersionPolicy versionPolicy = HttpVersionPolicy.AUTOMATIC; DnsResolver dnsResolver; HttpSocketFactory socketFactory = HttpSocketFactory::defaultSocketFactory; - int h2StreamsPerConnection = 100; final List listeners = new LinkedList<>(); /** @@ -47,9 +48,13 @@ public final class HttpConnectionPoolBuilder { *

      Each route (unique scheme+host+port+proxy combination) gets its own * connection pool with this capacity. * - *

      Note: Per-route limits only apply to HTTP/1.1 connections. - * HTTP/2 connections use multiplexing to handle many concurrent requests over - * fewer connections, so only {@link #maxTotalConnections(int)} applies to them. + *

      HTTP/1.1: This limits concurrent requests, since each connection + * handles one request at a time. + * + *

      HTTP/2: This limits physical connections. Maximum concurrent streams + * per route = {@code maxConnectionsPerRoute × h2StreamsPerConnection}. For example, + * with default settings (20 connections × 100 streams), a route can handle up to + * 2000 concurrent requests. * * @param max maximum connections per route, must be positive * @return this builder @@ -83,9 +88,14 @@ public HttpConnectionPoolBuilder maxConnectionsPerRoute(int max) { *

      Host matching is case-insensitive. If a port-specific limit is set, * it takes precedence over the host-only limit. * - *

      Note: Per-host limits only apply to HTTP/1.1 connections and are - * always capped by {@link #maxTotalConnections(int)}. If a per-host limit exceeds - * {@code maxTotalConnections}, the global limit takes precedence. + *

      HTTP/1.1: Limits concurrent requests to the host. + * + *

      HTTP/2: Limits physical connections to the host. Maximum concurrent + * streams = {@code maxConnectionsForHost × h2StreamsPerConnection}. For example, + * {@code maxConnectionsForHost("api.com", 5)} with {@code h2StreamsPerConnection(100)} + * allows up to 500 concurrent streams to that host. + * + *

      Note: Always capped by {@link #maxTotalConnections(int)}. * * @param host the hostname (with optional port), case-insensitive * @param max maximum connections for this specific host, must be positive @@ -371,12 +381,18 @@ public HttpConnectionPoolBuilder socketFactory(HttpSocketFactory socketFactory) /** * Set maximum concurrent streams per HTTP/2 connection before creating a new connection (default: 100). * - *

      This is a soft limit that controls when the pool creates additional HTTP/2 connections + *

      This is a soft limit that controls when the pool creates additional HTTP/2 connections * to spread load. When an existing connection reaches this many active streams, the pool - * will create a new connection for the next request (subject to {@link #maxTotalConnections(int)}). + * will prefer to create a new connection for the next request (subject to {@link #maxConnectionsPerRoute(int)} + * and {@link #maxTotalConnections(int)}). + * + *

      Important: This limit can be exceeded when the connection limit is reached. If all + * connections are at or above this soft limit, the pool will still multiplex additional streams + * on existing connections rather than blocking or failing, up to the server's hard limit + * ({@code SETTINGS_MAX_CONCURRENT_STREAMS}). * *

      This is distinct from the server's {@code SETTINGS_MAX_CONCURRENT_STREAMS}, which is - * a hard limit enforced by the server. This client-side limit helps balance load across + * a hard limit enforced by the server. This client-side soft limit helps balance load across * multiple connections to reduce lock contention and improve throughput under high concurrency. * *

      RFC 7540 Section 6.5.2 From 6d12b7639a85640675bba422850d498e0b36e0ce Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Tue, 9 Dec 2025 15:56:30 -0600 Subject: [PATCH 26/60] Improve H2 conn management and read timeouts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Connection pool changes: - Track pending connection creations to prevent over-creation under load - Preserve original exception when connection creation fails - Always notify waiters after connection creation (success or failure) Read timeout improvements: - Move from per-stream timed waits to centralized timeout sweep (~100ms) - Add readSeq activity counter to detect stale timeout decisions - Use AtomicBoolean CAS for at-most-once timeout semantics - Clear deadline on stream completion to prevent spurious timeouts The timeout sweep approach scales better with high VT counts by avoiding thousands of concurrent timed wait registrations. Timeouts are approximate (±100ms) which is acceptable for network I/O. --- .../client/VirtualThreadScalingBenchmark.java | 80 ++++++++++++++---- .../java/http/client/BenchmarkServer.java | 74 +++++++++++++--- .../connection/H2ConnectionManager.java | 84 +++++++++++-------- .../client/connection/HttpConnectionPool.java | 5 +- .../smithy/java/http/client/h1/H1Utils.java | 3 +- .../java/http/client/h2/H2Exchange.java | 71 +++++++++++++--- .../smithy/java/http/client/h2/H2Muxer.java | 61 +++++++++++++- .../java/http/client/h2/StreamRegistry.java | 22 +++-- .../http/client/h2/hpack/HpackDecoder.java | 3 +- 9 files changed, 319 insertions(+), 84 deletions(-) diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java index b99692d15..364715b6b 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java @@ -31,6 +31,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.time.Duration; @@ -107,9 +108,25 @@ @State(Scope.Benchmark) public class VirtualThreadScalingBenchmark { - @Param({"10000"}) + // Note: this takes _forever_, so comment out whatever you don't want to try if you want it to go faster. + @Param({ + //"100", "1000", "5000", "10000", "20000", + "30000"}) private int concurrency; + @Param({//"1", "2", "3", "4", "5", "10", + "20", + //"50" + }) + private int connectionLimit; + + @Param({ + // "100", "1024", "2048", + "4096" + // , "8192" + }) + private int streamsLimit; + // Server URL - use jmhWithServer task or set manually // For h2c: "http://localhost:18081" (default) // For h2 TLS: "https://localhost:18443" @@ -207,17 +224,7 @@ public void setup() throws Exception { // ===== HTTP/2 Cleartext Clients (h2c) ===== - // Smithy H2c client (cleartext, prior knowledge) - smithyClientH2c = HttpClient.builder() - .connectionPool(HttpConnectionPool.builder() - .maxConnectionsPerRoute(5000) - .maxTotalConnections(5000) - .maxIdleTime(Duration.ofMinutes(2)) - .dnsResolver(staticDns) - .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) - .h2StreamsPerConnection(100) - .build()) - .build(); + // Smithy H2c client - created per iteration in setupIteration() // Helidon H2c client (cleartext, prior knowledge) helidonClientH2c = Http2Client.builder() @@ -237,6 +244,7 @@ public void setup() throws Exception { .dnsResolver(staticDns) .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_2) .sslContext(trustAllSslContext) + .h2StreamsPerConnection(100) .build()) .build(); @@ -315,9 +323,7 @@ public void teardown() throws Exception { } // Close H2c clients - if (smithyClientH2c != null) { - smithyClientH2c.close(); - } + // smithyClientH2c closed per-iteration in setupIteration() if (helidonClientH2c != null) { helidonClientH2c.closeResource(); } @@ -349,6 +355,50 @@ public void reset() { } } + @Setup(Level.Iteration) + public void setupIteration() throws Exception { + if (smithyClientH2c != null) { + smithyClientH2c.close(); + } + + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + + System.out.println("Creating client: connectionLimit=" + connectionLimit + ", streamsLimit=" + streamsLimit); + + smithyClientH2c = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(connectionLimit) + .h2StreamsPerConnection(streamsLimit) + .maxIdleTime(Duration.ofMinutes(2)) + .dnsResolver(staticDns) + .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) + .build()) + .build(); + + try (var res = smithyClientH2c.send(HttpRequest.builder() + .uri(URI.create(h2cBaseUrl + "/reset")) + .method("POST") + .build())) { + res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); + } + Thread.sleep(100); + } + + @TearDown(Level.Iteration) + public void teardownIteration() throws Exception { + try (var res = smithyClientH2c.send(HttpRequest.builder() + .uri(URI.create(h2cBaseUrl + "/stats")) + .method("GET") + .build())) { + String stats = + new String(res.body().asInputStream().readAllBytes(), StandardCharsets.UTF_8); + System.out.println("Server stats [connectionLimit=" + connectionLimit + ", streamsLimit=" + streamsLimit + + "]: " + stats); + } + } + // ===== HTTP/1.1 Benchmarks ===== /** diff --git a/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java index 12e9aa302..9df22f60b 100644 --- a/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java +++ b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java @@ -22,6 +22,7 @@ import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.WriteBufferWaterMark; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; @@ -97,9 +98,9 @@ public BenchmarkServer(int h1Port, int h2Port, int h2cPort) throws Exception { this.h2Port = h2Port; this.h2cPort = h2cPort; + int cores = Runtime.getRuntime().availableProcessors(); bossGroup = new NioEventLoopGroup(1); - // Use more worker threads for high concurrency - workerGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() * 2); + workerGroup = new NioEventLoopGroup(cores * 4); // Start HTTP/1.1 server h1ServerChannel = startH1Server(h1Port); @@ -187,25 +188,26 @@ private Channel startH2cServer(int port) throws InterruptedException { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) - .option(ChannelOption.SO_BACKLOG, 16384) + .option(ChannelOption.SO_BACKLOG, 65536) .option(ChannelOption.SO_REUSEADDR, true) .childOption(ChannelOption.SO_KEEPALIVE, true) .childOption(ChannelOption.TCP_NODELAY, true) - .childOption(ChannelOption.SO_RCVBUF, 1048576) - .childOption(ChannelOption.SO_SNDBUF, 1048576) + .childOption(ChannelOption.SO_RCVBUF, 2097152) + .childOption(ChannelOption.SO_SNDBUF, 2097152) + .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(32768, 65536)) .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) { - // h2c prior knowledge: HTTP/2 directly without TLS or upgrade - // Large initial window size for high throughput var settings = io.netty.handler.codec.http2.Http2Settings.defaultSettings() - .maxConcurrentStreams(10000) - .initialWindowSize(1048576); // 1MB stream window + .maxConcurrentStreams(20000) + .initialWindowSize(2097152); ch.pipeline() .addLast( Http2FrameCodecBuilder.forServer() .initialSettings(settings) + .autoAckSettingsFrame(true) + .autoAckPingFrame(true) .build(), Http2RequestHandler.INSTANCE); } @@ -296,15 +298,67 @@ private static class Http2RequestHandler extends ChannelInboundHandlerAdapter { .set("content-type", "application/json") .setInt("content-length", CONTENT.length); + private static final java.util.concurrent.atomic.AtomicInteger connectionCount = + new java.util.concurrent.atomic.AtomicInteger(); + private static final java.util.concurrent.ConcurrentHashMap streamCounts = + new java.util.concurrent.ConcurrentHashMap<>(); + + @Override + public void handlerAdded(ChannelHandlerContext ctx) { + int count = connectionCount.incrementAndGet(); + String connId = ctx.channel().id().asShortText(); + streamCounts.put(connId, new java.util.concurrent.atomic.AtomicInteger()); + System.out.println("[H2] Connection opened: " + connId + " (total: " + count + ")"); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) { + int count = connectionCount.decrementAndGet(); + String connId = ctx.channel().id().asShortText(); + java.util.concurrent.atomic.AtomicInteger streams = streamCounts.remove(connId); + System.out.println("[H2] Connection closed: " + connId + " (handled " + + (streams != null ? streams.get() : 0) + " streams, remaining: " + count + ")"); + } + @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { if (msg instanceof Http2HeadersFrame headersFrame) { - if (headersFrame.isEndStream()) { + CharSequence path = headersFrame.headers().path(); + if ("/reset".contentEquals(path)) { + System.gc(); + System.out.println("[H2] Reset - Active connections: " + connectionCount.get()); + streamCounts + .forEach((id, count) -> System.out.println(" " + id + ": " + count.get() + " streams")); + Http2Headers resetHeaders = new DefaultHttp2Headers(true, 1).status("200"); + ctx.writeAndFlush(new DefaultHttp2HeadersFrame(resetHeaders, true).stream(headersFrame.stream())); + } else if ("/stats".contentEquals(path)) { + StringBuilder json = new StringBuilder("{\"connections\":"); + json.append(connectionCount.get()).append(",\"streams\":{"); + streamCounts.forEach( + (id, count) -> json.append("\"").append(id).append("\":").append(count.get()).append(",")); + if (json.charAt(json.length() - 1) == ',') + json.setLength(json.length() - 1); + json.append("}}"); + byte[] body = json.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8); + Http2Headers statsHeaders = new DefaultHttp2Headers(true, 2) + .status("200") + .setInt("content-length", body.length); + ctx.write(new DefaultHttp2HeadersFrame(statsHeaders, false).stream(headersFrame.stream())); + ctx.writeAndFlush(new DefaultHttp2DataFrame(Unpooled.wrappedBuffer(body), true) + .stream(headersFrame.stream())); + } else if (headersFrame.isEndStream()) { + var counter = streamCounts.get(ctx.channel().id().asShortText()); + if (counter != null) + counter.incrementAndGet(); sendResponse(ctx, headersFrame.stream()); } } else if (msg instanceof Http2DataFrame dataFrame) { dataFrame.release(); if (dataFrame.isEndStream()) { + var counter = streamCounts.get(ctx.channel().id().asShortText()); + if (counter != null) + counter.incrementAndGet(); sendResponse(ctx, dataFrame.stream()); } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java index d786397b3..d758b8caa 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java @@ -25,6 +25,8 @@ final class H2ConnectionManager { */ private static final class RouteState { volatile H2Connection[] conns = new H2Connection[0]; + // Number of connections currently being created (to prevent over-creation) + int pendingCreations = 0; } private static final H2Connection[] EMPTY = new H2Connection[0]; @@ -66,39 +68,36 @@ private RouteState stateFor(Route route) { */ H2Connection acquire(Route route, int maxConnectionsForRoute) throws IOException { RouteState state = stateFor(route); - - // Fast path: snapshot of current connections, no locking - H2Connection conn = tryAcquire(state.conns); - if (conn != null) { - notifyAcquire(conn, true); - return conn; - } - long deadline = System.currentTimeMillis() + acquireTimeoutMs; + boolean shouldCreate = false; - // Slow path: check existing under lock, wait if at limit synchronized (state) { while (true) { - // Try to find a connection under the soft limit (streamsPerConnection) H2Connection[] snapshot = state.conns; - H2Connection rechecked = tryAcquireUnderLimit(snapshot); - if (rechecked != null) { - notifyAcquire(rechecked, true); - return rechecked; - } else if (snapshot.length < maxConnectionsForRoute) { - // Under connection limit: exit loop to create new connection + int totalConns = snapshot.length + state.pendingCreations; + + // Try to find a connection under the soft limit + H2Connection conn = tryAcquireUnderLimit(snapshot); + if (conn != null) { + notifyAcquire(conn, true); + return conn; + } + + // All connections at/above soft limit - create new if under connection limit + if (totalConns < maxConnectionsForRoute) { + state.pendingCreations++; + shouldCreate = true; break; } - // At connection limit, so try to reuse any connection even if over soft limit - // (but still under server's SETTINGS_MAX_CONCURRENT_STREAMS hard limit) - H2Connection overLimit = tryAcquire(snapshot); - if (overLimit != null) { - notifyAcquire(overLimit, true); - return overLimit; + // At connection limit - use any connection even if over soft limit + conn = tryAcquire(snapshot); + if (conn != null) { + notifyAcquire(conn, true); + return conn; } - // No connections available, so wait for one to free up capacity. + // Wait for capacity long remaining = deadline - System.currentTimeMillis(); if (remaining <= 0) { throw new IOException("Acquire timeout: no connection available after " @@ -106,7 +105,6 @@ H2Connection acquire(Route route, int maxConnectionsForRoute) throws IOException } try { - // Wait and then retry the loop. state.wait(remaining); } catch (InterruptedException e) { Thread.currentThread().interrupt(); @@ -115,22 +113,38 @@ H2Connection acquire(Route route, int maxConnectionsForRoute) throws IOException } } - return createNewH2Connection(route, state); + if (shouldCreate) { + return createNewH2Connection(route, state); + } + throw new IllegalStateException("unreachable"); } @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") private H2Connection createNewH2Connection(Route route, RouteState state) throws IOException { - // Create new connection OUTSIDE the lock to avoid deadlock. Note: Multiple threads may race to create - // connections for the same route. This is acceptable: we'd rather over-create slightly than deadlock. - H2Connection newConn = connectionFactory.create(route); + // Create new connection OUTSIDE the lock to avoid deadlock. + H2Connection newConn = null; + IOException createException = null; + try { + newConn = connectionFactory.create(route); + } catch (IOException e) { + createException = e; + } finally { + // Register under lock (or decrement pending on failure) + synchronized (state) { + state.pendingCreations--; + if (newConn != null) { + H2Connection[] cur = state.conns; + H2Connection[] next = new H2Connection[cur.length + 1]; + System.arraycopy(cur, 0, next, 0, cur.length); + next[cur.length] = newConn; + state.conns = next; + } + state.notifyAll(); // Wake waiters + } + } - // Register under lock - synchronized (state) { - H2Connection[] cur = state.conns; - H2Connection[] next = new H2Connection[cur.length + 1]; - System.arraycopy(cur, 0, next, 0, cur.length); - next[cur.length] = newConn; - state.conns = next; + if (createException != null) { + throw createException; } notifyAcquire(newConn, false); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java index d67449b5b..de01fb5de 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java @@ -184,7 +184,10 @@ public final class HttpConnectionPool implements ConnectionPool { this.h1Manager = new H1ConnectionManager(this.maxIdleTimeNanos); this.connectionPermits = new Semaphore(builder.maxTotalConnections, false); this.listeners = List.copyOf(builder.listeners); - this.h2Manager = new H2ConnectionManager(builder.h2StreamsPerConnection, this.acquireTimeoutMs, listeners, this::onNewH2Connection); + this.h2Manager = new H2ConnectionManager(builder.h2StreamsPerConnection, + this.acquireTimeoutMs, + listeners, + this::onNewH2Connection); this.cleanupThread = Thread.ofVirtual().name("http-pool-cleanup").start(this::cleanupIdleConnections); } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Utils.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Utils.java index 1ba3ccaec..d7430eb65 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Utils.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Utils.java @@ -5,6 +5,7 @@ package software.amazon.smithy.java.http.client.h1; +import java.nio.charset.StandardCharsets; import software.amazon.smithy.java.http.api.HeaderNames; import software.amazon.smithy.java.http.api.ModifiableHttpHeaders; @@ -52,7 +53,7 @@ static String parseHeaderLine(byte[] buf, int len, ModifiableHttpHeaders headers valueEnd--; } - String value = new String(buf, valueStart, valueEnd - valueStart, java.nio.charset.StandardCharsets.US_ASCII); + String value = new String(buf, valueStart, valueEnd - valueStart, StandardCharsets.US_ASCII); headers.addHeader(name, value); return name; } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index b8794126d..bdd38db7a 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import software.amazon.smithy.java.http.api.HttpHeaders; @@ -119,6 +120,9 @@ enum ReadState { // Stream-level timeouts private final long readTimeoutMs; private final long writeTimeoutMs; + private final AtomicLong readSeq = new AtomicLong(); // Activity counter, incremented on read activity + private volatile long readDeadlineNanos; // 0 = no deadline, >0 = deadline in nanos + private final AtomicBoolean readTimedOut = new AtomicBoolean(); // At-most-once timeout flag // Response state private volatile int statusCode = -1; @@ -196,6 +200,53 @@ int getStreamId() { return streamId; } + /** + * Get read timeout in milliseconds. + */ + long getReadTimeoutMs() { + return readTimeoutMs; + } + + /** + * Get read deadline in nanoseconds (0 = no deadline). + */ + long getReadDeadlineNanos() { + return readDeadlineNanos; + } + + /** + * Get read activity sequence number. + */ + long getReadSeq() { + return readSeq.get(); + } + + /** + * Attempt to mark this exchange as timed out. Returns true if successful (first caller wins). + * Used by timeout sweep to ensure at-most-once timeout per exchange. + */ + boolean markReadTimedOut() { + return readTimedOut.compareAndSet(false, true); + } + + /** + * Record read activity: bump sequence and reset deadline. + * Called when headers or data arrive. + */ + private void onReadActivity() { + if (readTimeoutMs > 0) { + readSeq.incrementAndGet(); + readDeadlineNanos = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(readTimeoutMs); + } + } + + /** + * Clear read deadline (no timeout). + */ + private void clearReadDeadline() { + readDeadlineNanos = 0; + } + /** * Get the muxer for this exchange. */ @@ -322,11 +373,14 @@ void commitWrite(int bytesWritten, boolean endStream) { dataLock.lock(); try { writePos += bytesWritten; + if (bytesWritten > 0) { + onReadActivity(); // Extend timeout when data arrives + } if (endStream) { this.endStreamReceived = true; this.readState = ReadState.DONE; - // Don't update streamState here - it will be updated when - // the user finishes reading and we return -1 + clearReadDeadline(); // No more data expected, clear timeout + // Don't update streamState here. It will be updated when the user finishes reading, and we return -1. } dataAvailable.signalAll(); } finally { @@ -548,10 +602,7 @@ private void awaitEvent() throws IOException { try { // Wait for headers, error, or data (which also signals) while (pendingHeaders == null && readState != ReadState.ERROR && readState != ReadState.DONE) { - if (!dataAvailable.await(readTimeoutMs, TimeUnit.MILLISECONDS)) { - throw new SocketTimeoutException( - "Read timed out after " + readTimeoutMs + "ms waiting for response"); - } + dataAvailable.await(); // Untimed: muxer watchdog handles timeout } // Check for error @@ -573,6 +624,8 @@ private void awaitEvent() throws IOException { * HPACK dynamic table consistency across all streams. */ private void readResponseHeaders() throws IOException { + onReadActivity(); // Start timeout when beginning to read response + while (!responseHeadersReceived) { awaitEvent(); @@ -626,6 +679,7 @@ private void handleHeadersEvent(List fields, boolean isEndStream) t if (isEndStream) { endStreamReceived = true; readState = ReadState.DONE; + clearReadDeadline(); // No more data expected updateStreamStateOnEndStream(); validateContentLength(); } else if (responseHeadersReceived && readState != ReadState.DONE) { @@ -949,10 +1003,7 @@ int readFromBuffer(byte[] buf, int off, int len) throws IOException { } try { - if (!dataAvailable.await(readTimeoutMs, TimeUnit.MILLISECONDS)) { - throw new SocketTimeoutException( - "Read timed out after " + readTimeoutMs + "ms waiting for data"); - } + dataAvailable.await(); // Untimed: muxer watchdog handles timeout } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException("Interrupted waiting for data", e); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java index 4d2037996..37bf42495 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java @@ -126,6 +126,9 @@ enum ControlFrameType { "proxy-authorization", "set-cookie"); + // How often to check for read timeouts (every ~100ms) + private static final long READ_TIMOUT_FREQUENCY = TimeUnit.MILLISECONDS.toNanos(100); + // Singleton wake-up signal private static final WorkItem.CheckDataQueue CHECK_DATA_QUEUE = new WorkItem.CheckDataQueue(); @@ -218,7 +221,7 @@ H2Exchange newExchange(HttpRequest request, long readTimeoutMs, long writeTimeou void closeExchanges(Duration timeout) { accepting = false; - streams.forEach(exchange -> exchange.signalConnectionClosed(null)); + streams.forEach(null, (exchange, _ignore) -> exchange.signalConnectionClosed(null)); long deadline = System.nanoTime() + timeout.toNanos(); while (activeStreamCount.get() > 0 && System.nanoTime() < deadline) { @@ -234,7 +237,9 @@ void closeExchanges(Duration timeout) { streams.clearAndClose(exchange -> { try { exchange.close(); - } catch (Exception ignored) {} + } catch (Exception ignored) { + // trying to close, ignore failure + } }); activeStreamCount.set(0); } @@ -299,7 +304,7 @@ int allocateAndRegisterStream(H2Exchange exchange) { void onConnectionClosing(Throwable error) { accepting = false; - streams.forEach(exchange -> exchange.signalConnectionClosed(error)); + streams.forEach(error, H2Exchange::signalConnectionClosed); } void onSettingsReceived(int maxConcurrentStreams, int initialWindowSize, int maxFrameSize) { @@ -309,7 +314,7 @@ void onSettingsReceived(int maxConcurrentStreams, int initialWindowSize, int max int delta = initialWindowSize - this.remoteInitialWindowSize; this.remoteInitialWindowSize = initialWindowSize; if (delta != 0) { - streams.forEach(exchange -> exchange.adjustSendWindow(delta)); + streams.forEach(delta, H2Exchange::adjustSendWindow); } } @@ -455,11 +460,52 @@ IOException getWriteError() { return writeError; } + /** + * Check all active streams for read timeouts, called periodically from the worker loop. + * + *

      Read timeouts are approximate: ±100ms due to the polling interval. This is acceptable because network + * I/O already has inherent latency variance, and callers setting a "30s timeout" don't expect millisecond + * precision. + * + *

      There is an unavoidable race: data could arrive just after we decide to timeout but before we signal. + * We mitigate this by checking both deadline and activity sequence twice - we only timeout if the stream + * appears expired and idle across two snapshots. The remaining race window is small and acceptable because + * timeouts are approximate and failure is recoverable at the caller layer. + */ + private void checkReadTimeouts(long nowNanos) { + streams.forEach(nowNanos, H2Muxer::checkExchangeTimeout); + } + + private static void checkExchangeTimeout(H2Exchange exchange, long nowNanos) { + long seq1 = exchange.getReadSeq(); + long d1 = exchange.getReadDeadlineNanos(); + if (d1 <= 0 || nowNanos <= d1) { + return; + } + + // Second snapshot: did anything change while we were looking? + long seq2 = exchange.getReadSeq(); + long d2 = exchange.getReadDeadlineNanos(); + if (seq1 != seq2 || d2 <= 0 || nowNanos <= d2) { + return; + } + + // Try to claim the timeout - only first caller wins + if (!exchange.markReadTimedOut()) { + return; + } + + exchange.signalConnectionClosed(new SocketTimeoutException( + "Read timeout: no data received for " + exchange.getReadTimeoutMs() + "ms")); + } + // ==================== WRITER THREAD ==================== private void workerLoop() { var batch = new ArrayList(64); IOException failure = null; + long lastTimeoutCheck = System.nanoTime(); + var readTimeoutFrequency = READ_TIMOUT_FREQUENCY; try { while (running) { @@ -504,6 +550,13 @@ private void workerLoop() { return; } } + + // Check for read timeouts periodically + long now = System.nanoTime(); + if (now - lastTimeoutCheck > readTimeoutFrequency) { + checkReadTimeouts(now); + lastTimeoutCheck = now; + } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamRegistry.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamRegistry.java index 33d7a0b40..2ab56beff 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamRegistry.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamRegistry.java @@ -7,7 +7,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.IntPredicate; /** * Essentially a very fast custom hashmap of stream ID to H2Exchange. @@ -30,6 +32,9 @@ * *

      We map stream IDs to array slots via: {@code slot = ((streamId - 1) >>> 1) & slotMask}. This gives O(1) lookup * without hashing or Integer boxing overhead. + * + *

      This class is a thread-safe registry and does not enforce any stream lifecycle policies + * (timeouts, errors, etc). Callers are responsible for managing timeouts and cleanup using forEach / clearAndClose. */ final class StreamRegistry { @@ -109,22 +114,25 @@ boolean remove(int streamId) { } /** - * Iterate over all active exchanges. - * Used for cold paths (cleanup, settings changes, connection close). + * Iterate over all active exchanges with a context value. + * Avoids lambda allocation by passing context to a BiConsumer. * * @param action the action to perform on each exchange + * @param context context value passed to each invocation + * @param the context type */ - void forEach(Consumer action) { - // Iterate Array and the spillover map + void forEach(T context, BiConsumer action) { for (int i = 0; i < SLOTS; i++) { H2Exchange exchange = fastPath.get(i); if (exchange != null) { - action.accept(exchange); + action.accept(exchange, context); } } if (!spillover.isEmpty()) { - spillover.values().forEach(action); + for (H2Exchange exchange : spillover.values()) { + action.accept(exchange, context); + } } } @@ -134,7 +142,7 @@ void forEach(Consumer action) { * @param predicate condition to check * @param action the action to perform on matching exchanges */ - void forEachMatching(java.util.function.IntPredicate predicate, Consumer action) { + void forEachMatching(IntPredicate predicate, Consumer action) { // Iterate Array and spillover map. for (int i = 0; i < SLOTS; i++) { H2Exchange exchange = fastPath.get(i); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java index 08aa2f57f..fb894aa64 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java @@ -6,6 +6,7 @@ package software.amazon.smithy.java.http.client.h2.hpack; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import software.amazon.smithy.java.http.api.HeaderNames; @@ -163,7 +164,7 @@ private String decodeString(byte[] data) throws IOException { if (huffman) { str = Huffman.decode(data, decodePos, length); } else { - str = new String(data, decodePos, length, java.nio.charset.StandardCharsets.ISO_8859_1); + str = new String(data, decodePos, length, StandardCharsets.ISO_8859_1); } decodePos += length; From 58e6fc6f77efe65a7aca28b78469f139c5a7b113 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Tue, 9 Dec 2025 19:36:31 -0600 Subject: [PATCH 27/60] Rewrite benchmarks, improve read timeout handling --- http/http-client/build.gradle.kts | 18 +- .../java/http/client/BenchmarkSupport.java | 177 ++++ .../java/http/client/H1ScalingBenchmark.java | 203 +++++ .../java/http/client/H2cScalingBenchmark.java | 381 ++++++++ .../client/VirtualThreadScalingBenchmark.java | 842 ------------------ .../java/http/client/BenchmarkServer.java | 72 +- .../connection/HttpConnectionFactory.java | 7 +- .../client/connection/HttpConnectionPool.java | 3 +- .../connection/HttpConnectionPoolBuilder.java | 31 + .../java/http/client/h2/H2Connection.java | 34 +- .../java/http/client/h2/H2Exchange.java | 41 +- .../smithy/java/http/client/h2/H2Muxer.java | 63 +- 12 files changed, 969 insertions(+), 903 deletions(-) create mode 100644 http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/BenchmarkSupport.java create mode 100644 http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java create mode 100644 http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java delete mode 100644 http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java diff --git a/http/http-client/build.gradle.kts b/http/http-client/build.gradle.kts index 1ed6588b2..21af5d4e3 100644 --- a/http/http-client/build.gradle.kts +++ b/http/http-client/build.gradle.kts @@ -130,26 +130,22 @@ val stopBenchmarkServer by tasks.registering { } // Configure JMH +// Run with: ./gradlew :http:http-client:jmh -Pjmh.includes="H2cScalingBenchmark.smithy" +// To customize params, edit @Param annotations in benchmark source files jmh { - includes = listOf(".*smithyH2c.*") + val includesProp = project.findProperty("jmh.includes")?.toString() + includes = if (includesProp != null) listOf(includesProp) else listOf(".*") warmupIterations = 3 iterations = 3 fork = 1 // profilers.add("async:output=flamegraph") -// profilers.add("async:output=collapsed") + profilers.add("async:output=collapsed") // profilers.add("gc") } -// Task that runs JMH with external server (starts server, runs jmh, stops server) -val jmhWithServer by tasks.registering(Exec::class) { +// Make jmh task auto-start/stop the benchmark server +tasks.named("jmh") { dependsOn(startBenchmarkServer) finalizedBy(stopBenchmarkServer) - notCompatibleWithConfigurationCache("Runs JMH with external server") - - workingDir = project.rootDir - commandLine( - "./gradlew", - ":http:http-client:jmh", - ) } diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/BenchmarkSupport.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/BenchmarkSupport.java new file mode 100644 index 000000000..7d29d44f3 --- /dev/null +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/BenchmarkSupport.java @@ -0,0 +1,177 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.client.dns.DnsResolver; + +/** + * Shared utilities for HTTP client benchmarks. + * This class is not a benchmark - JMH only measures @Benchmark methods. + */ +public final class BenchmarkSupport { + + public static final String H1_URL = "http://localhost:18080"; + public static final String H2C_URL = "http://localhost:18081"; + public static final String H2_URL = "https://localhost:18443"; + + // Small JSON payload for POST benchmarks + public static final byte[] POST_PAYLOAD = "{\"id\":12345,\"name\":\"benchmark\"}".getBytes(StandardCharsets.UTF_8); + + // 1MB payload for large transfer benchmarks + public static final byte[] MB_PAYLOAD = new byte[1024 * 1024]; + + private BenchmarkSupport() {} + + /** + * Create a DNS resolver that maps localhost to loopback, avoiding DNS overhead. + */ + public static DnsResolver staticDns() { + return DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + } + + /** + * Create an SSL context that trusts all certificates (for benchmarking only). + */ + public static SSLContext trustAllSsl() throws Exception { + TrustManager[] trustAllCerts = new TrustManager[] { + new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void checkClientTrusted(X509Certificate[] certs, String authType) {} + + public void checkServerTrusted(X509Certificate[] certs, String authType) {} + } + }; + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, new SecureRandom()); + return sslContext; + } + + /** + * Reset server state and trigger GC. + */ + public static void resetServer(HttpClient client, String baseUrl) throws Exception { + try (var res = client.send(HttpRequest.builder() + .uri(URI.create(baseUrl + "/reset")) + .method("POST") + .build())) { + res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); + } + Thread.sleep(100); + } + + /** + * Get server stats as JSON string. + */ + public static String getServerStats(HttpClient client, String baseUrl) throws Exception { + try (var res = client.send(HttpRequest.builder() + .uri(URI.create(baseUrl + "/stats")) + .method("GET") + .build())) { + return new String(res.body().asInputStream().readAllBytes(), StandardCharsets.UTF_8); + } + } + + /** + * Run a benchmark loop with virtual threads. + * + * @param concurrency number of virtual threads + * @param durationMs how long to run + * @param task the task each thread runs in a loop + * @param context context passed to task (avoids lambda allocation) + * @param counter output counter for requests/errors + */ + public static void runBenchmark( + int concurrency, + long durationMs, + BenchmarkTask task, + T context, + RequestCounter counter + ) throws InterruptedException { + var requests = new AtomicLong(); + var errors = new AtomicLong(); + var firstError = new AtomicReference(); + var running = new AtomicBoolean(true); + var latch = new CountDownLatch(concurrency); + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < concurrency; i++) { + executor.submit(() -> { + try { + while (running.get()) { + task.run(context); + requests.incrementAndGet(); + } + } catch (Exception e) { + errors.incrementAndGet(); + firstError.compareAndSet(null, e); + } finally { + latch.countDown(); + } + }); + } + + Thread.sleep(durationMs); + running.set(false); + // Don't wait long - VTs may be blocked on in-flight requests + latch.await(100, TimeUnit.MILLISECONDS); + } + + counter.requests = requests.get(); + counter.errors = errors.get(); + counter.firstError = firstError.get(); + } + + @FunctionalInterface + public interface BenchmarkTask { + void run(T context) throws Exception; + } + + /** + * Simple counter for benchmark results. Used with @AuxCounters. + */ + public static class RequestCounter { + public long requests; + public long errors; + public Throwable firstError; + + public void reset() { + requests = 0; + errors = 0; + firstError = null; + } + + public void logErrors(String label) { + if (firstError != null) { + System.err.println(label + " errors: " + errors + ", first:"); + firstError.printStackTrace(System.err); + } + } + } +} diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java new file mode 100644 index 000000000..3058c34ab --- /dev/null +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java @@ -0,0 +1,203 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import io.helidon.webclient.api.HttpClientResponse; +import io.helidon.webclient.api.WebClient; +import java.net.URI; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.util.Timeout; +import org.openjdk.jmh.annotations.AuxCounters; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.io.datastream.DataStream; + +/** + * HTTP/1.1 client scaling benchmark. + * + *

      For H1, the key parameters are: + *

        + *
      • concurrency - number of virtual threads making requests
      • + *
      • maxConnections - connection pool size (caps actual parallelism)
      • + *
      + * + *

      Run with: ./gradlew :http:http-client:jmh -Pjmh.includes="H1ScalingBenchmark" + */ +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +@Warmup(iterations = 2, time = 3) +@Measurement(iterations = 3, time = 5) +@Fork(value = 1, jvmArgs = {"-Xms2g", "-Xmx2g"}) +@State(Scope.Benchmark) +public class H1ScalingBenchmark { + + @Param({"100", "500", "1000", "2000"}) + private int concurrency; + + @Param({"50", "100", "200", "500"}) + private int maxConnections; + + private HttpClient smithyClient; + private CloseableHttpClient apacheClient; + private WebClient helidonClient; + + @Setup(Level.Iteration) + public void setupIteration() throws Exception { + closeClients(); + + System.out.println("H1 setup: concurrency=" + concurrency + ", maxConnections=" + maxConnections); + + // Smithy client + smithyClient = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(maxConnections) + .maxTotalConnections(maxConnections) + .maxIdleTime(Duration.ofMinutes(2)) + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .dnsResolver(BenchmarkSupport.staticDns()) + .build()) + .build(); + + // Apache client + PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(); + connManager.setMaxTotal(maxConnections); + connManager.setDefaultMaxPerRoute(maxConnections); + connManager.setDefaultConnectionConfig(ConnectionConfig.custom() + .setConnectTimeout(Timeout.ofSeconds(10)) + .setSocketTimeout(Timeout.ofSeconds(30)) + .build()); + + apacheClient = HttpClients.custom() + .setConnectionManager(connManager) + .setDefaultRequestConfig(RequestConfig.custom() + .setConnectionRequestTimeout(Timeout.ofSeconds(5)) + .build()) + .build(); + + // Helidon client + helidonClient = WebClient.builder() + .baseUri(BenchmarkSupport.H1_URL) + .shareConnectionCache(false) + .connectionCacheSize(maxConnections) + .build(); + + BenchmarkSupport.resetServer(smithyClient, BenchmarkSupport.H1_URL); + } + + @TearDown(Level.Iteration) + public void teardownIteration() throws Exception { + String stats = BenchmarkSupport.getServerStats(smithyClient, BenchmarkSupport.H1_URL); + System.out.println("H1 stats [c=" + concurrency + ", conn=" + maxConnections + "]: " + stats); + } + + @TearDown + public void teardown() throws Exception { + closeClients(); + } + + private void closeClients() throws Exception { + if (smithyClient != null) { + smithyClient.close(); + smithyClient = null; + } + if (apacheClient != null) { + apacheClient.close(); + apacheClient = null; + } + if (helidonClient != null) { + helidonClient.closeResource(); + helidonClient = null; + } + } + + @AuxCounters(AuxCounters.Type.EVENTS) + @State(Scope.Thread) + public static class Counter extends BenchmarkSupport.RequestCounter { + @Setup(Level.Iteration) + public void reset() { + super.reset(); + } + } + + @Benchmark + @Threads(1) + public void smithy(Counter counter) throws InterruptedException { + var uri = URI.create(BenchmarkSupport.H1_URL + "/get"); + var request = HttpRequest.builder().uri(uri).method("GET").build(); + + BenchmarkSupport.runBenchmark(concurrency, 1000, (HttpRequest req) -> { + smithyClient.send(req).close(); + }, request, counter); + + counter.logErrors("Smithy H1"); + } + + @Benchmark + @Threads(1) + public void apache(Counter counter) throws InterruptedException { + var target = BenchmarkSupport.H1_URL + "/get"; + + BenchmarkSupport.runBenchmark(concurrency, 1000, (String url) -> { + try (var response = apacheClient.execute(new HttpGet(url))) { + EntityUtils.consume(response.getEntity()); + } + }, target, counter); + + counter.logErrors("Apache H1"); + } + + @Benchmark + @Threads(1) + public void helidon(Counter counter) throws InterruptedException { + BenchmarkSupport.runBenchmark(concurrency, 1000, (WebClient client) -> { + try (HttpClientResponse response = client.get("/get").request()) { + response.entity().consume(); + } + }, helidonClient, counter); + + counter.logErrors("Helidon H1"); + } + + @Benchmark + @Threads(1) + public void smithyPost(Counter counter) throws InterruptedException { + var uri = URI.create(BenchmarkSupport.H1_URL + "/post"); + var request = HttpRequest.builder() + .uri(uri) + .method("POST") + .body(DataStream.ofBytes(BenchmarkSupport.POST_PAYLOAD)) + .build(); + + BenchmarkSupport.runBenchmark(concurrency, 1000, (HttpRequest req) -> { + smithyClient.send(req).close(); + }, request, counter); + + counter.logErrors("Smithy H1 POST"); + } +} diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java new file mode 100644 index 000000000..ac7f78872 --- /dev/null +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java @@ -0,0 +1,381 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import io.helidon.webclient.api.HttpClientResponse; +import io.helidon.webclient.http2.Http2Client; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.Http2DataFrame; +import io.netty.handler.codec.http2.Http2FrameCodecBuilder; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import io.netty.handler.codec.http2.Http2MultiplexHandler; +import io.netty.handler.codec.http2.Http2StreamChannel; +import io.netty.handler.codec.http2.Http2StreamChannelBootstrap; +import io.netty.handler.codec.http2.Http2StreamFrame; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import org.openjdk.jmh.annotations.AuxCounters; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.io.datastream.DataStream; + +/** + * HTTP/2 cleartext (h2c) client scaling benchmark. + * + *

      For H2, the key parameters are: + *

        + *
      • concurrency - number of virtual threads making requests
      • + *
      • connections - number of H2 connections (each multiplexes many streams)
      • + *
      • streamsPerConnection - max concurrent streams per connection
      • + *
      + * + *

      Effective parallelism ≈ connections × streamsPerConnection. + * Set concurrency higher to measure backpressure behavior. + * + *

      Run with: ./gradlew :http:http-client:jmh -Pjmh.includes="H2cScalingBenchmark" + */ +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +@Warmup(iterations = 2, time = 3) +@Measurement(iterations = 3, time = 5) +@Fork(value = 1, jvmArgs = {"-Xms2g", "-Xmx2g"}) +@State(Scope.Benchmark) +public class H2cScalingBenchmark { + + @Param({"1000"}) + private int concurrency; + + @Param({"1" + //, "5" + //, "10", "20", "50" + }) + private int connections; + + @Param({"4096"}) + private int streamsPerConnection; + + private HttpClient smithyClient; + private Http2Client helidonClient; + + // Netty client state - multiple connections like Smithy + private EventLoopGroup nettyGroup; + private List nettyChannels; + private List nettyStreamBootstraps; + + @Setup(Level.Iteration) + public void setupIteration() throws Exception { + closeClients(); + + System.out.println("H2c setup: concurrency=" + concurrency + + ", connections=" + connections + + ", streams=" + streamsPerConnection); + + // Smithy H2c client + smithyClient = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(connections) + .maxTotalConnections(connections) + .h2StreamsPerConnection(streamsPerConnection) + .h2InitialWindowSize(1024 * 1024) + .maxIdleTime(Duration.ofMinutes(2)) + .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) + .dnsResolver(BenchmarkSupport.staticDns()) + .build()) + .build(); + + // Helidon H2c client + helidonClient = Http2Client.builder() + .baseUri(BenchmarkSupport.H2C_URL) + .shareConnectionCache(false) + .protocolConfig(pc -> pc.priorKnowledge(true)) + .build(); + + // Netty H2c client - create same number of connections as Smithy + nettyGroup = new NioEventLoopGroup(); + nettyChannels = new ArrayList<>(); + nettyStreamBootstraps = new ArrayList<>(); + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(nettyGroup) + .channel(NioSocketChannel.class) + .option(ChannelOption.SO_KEEPALIVE, true) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ch.pipeline() + .addLast( + Http2FrameCodecBuilder.forClient() + .initialSettings( + io.netty.handler.codec.http2.Http2Settings.defaultSettings() + .maxConcurrentStreams(100000) + .initialWindowSize(1024 * 1024)) + .build(), + new Http2MultiplexHandler(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0( + ChannelHandlerContext ctx, + Http2StreamFrame msg + ) {} + })); + } + }); + for (int i = 0; i < connections; i++) { + Channel ch = bootstrap.connect(new InetSocketAddress("localhost", 18081)).sync().channel(); + nettyChannels.add(ch); + nettyStreamBootstraps.add(new Http2StreamChannelBootstrap(ch)); + } + + BenchmarkSupport.resetServer(smithyClient, BenchmarkSupport.H2C_URL); + } + + @TearDown(Level.Iteration) + public void teardownIteration() throws Exception { + String stats = BenchmarkSupport.getServerStats(smithyClient, BenchmarkSupport.H2C_URL); + System.out.println("H2c stats [c=" + concurrency + ", conn=" + connections + + ", streams=" + streamsPerConnection + "]: " + stats); + } + + @TearDown + public void teardown() throws Exception { + closeClients(); + } + + private void closeClients() throws Exception { + if (smithyClient != null) { + smithyClient.close(); + smithyClient = null; + } + if (helidonClient != null) { + helidonClient.closeResource(); + helidonClient = null; + } + if (nettyChannels != null) { + for (Channel ch : nettyChannels) { + ch.close().sync(); + } + nettyChannels = null; + nettyStreamBootstraps = null; + } + if (nettyGroup != null) { + nettyGroup.shutdownGracefully().sync(); + nettyGroup = null; + } + } + + @AuxCounters(AuxCounters.Type.EVENTS) + @State(Scope.Thread) + public static class Counter extends BenchmarkSupport.RequestCounter { + @Setup(Level.Iteration) + public void reset() { + super.reset(); + } + } + + @Benchmark + @Threads(1) + public void smithy(Counter counter) throws InterruptedException { + var uri = URI.create(BenchmarkSupport.H2C_URL + "/get"); + var request = HttpRequest.builder().uri(uri).method("GET").build(); + + BenchmarkSupport.runBenchmark(concurrency, 1000, (HttpRequest req) -> { + try (var res = smithyClient.send(req)) { + res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); + } + }, request, counter); + + counter.logErrors("Smithy H2c"); + } + + @Benchmark + @Threads(1) + public void helidon(Counter counter) throws InterruptedException { + BenchmarkSupport.runBenchmark(concurrency, 1000, (Http2Client client) -> { + try (HttpClientResponse response = client.get("/get").request()) { + response.entity().consume(); + } + }, helidonClient, counter); + + counter.logErrors("Helidon H2c"); + } + + @Benchmark + @Threads(1) + public void smithyPost(Counter counter) throws InterruptedException { + var uri = URI.create(BenchmarkSupport.H2C_URL + "/post"); + var request = HttpRequest.builder() + .uri(uri) + .method("POST") + .body(DataStream.ofBytes(BenchmarkSupport.POST_PAYLOAD)) + .build(); + + BenchmarkSupport.runBenchmark(concurrency, 1000, (HttpRequest req) -> { + try (var res = smithyClient.send(req)) { + res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); + } + }, request, counter); + + counter.logErrors("Smithy H2c POST"); + } + + @Benchmark + @Threads(1) + public void smithyPutMb(Counter counter) throws InterruptedException { + var uri = URI.create(BenchmarkSupport.H2C_URL + "/putmb"); + var request = HttpRequest.builder() + .uri(uri) + .method("PUT") + .body(DataStream.ofBytes(BenchmarkSupport.MB_PAYLOAD)) + .build(); + + BenchmarkSupport.runBenchmark(concurrency, 1000, (HttpRequest req) -> { + try (var res = smithyClient.send(req)) { + res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); + } + }, request, counter); + + counter.logErrors("Smithy H2c PUT 1MB"); + } + + @Benchmark + @Threads(1) + public void smithyGetMb(Counter counter) throws InterruptedException { + var uri = URI.create(BenchmarkSupport.H2C_URL + "/getmb"); + var request = HttpRequest.builder().uri(uri).method("GET").build(); + + BenchmarkSupport.runBenchmark(concurrency, 1000, (HttpRequest req) -> { + try (var res = smithyClient.send(req)) { + res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); + } + }, request, counter); + + System.out.println("Smithy H2c GET 1MB: " + counter.requests + " requests, " + counter.errors + " errors"); + counter.logErrors("Smithy H2c GET 1MB"); + } + + @Benchmark + @Threads(1) + public void nettyGetMb(Counter counter) throws Exception { + var requests = new AtomicLong(); + var errors = new AtomicLong(); + var firstError = new AtomicReference(); + var running = new AtomicBoolean(true); + var activeTasks = new AtomicLong(concurrency); + + DefaultHttp2Headers headers = new DefaultHttp2Headers(); + headers.method("GET"); + headers.path("/getmb"); + headers.scheme("http"); + headers.authority("localhost:18081"); + + var connectionIndex = new AtomicInteger(0); + Runnable[] makeRequest = new Runnable[1]; + makeRequest[0] = () -> { + if (!running.get()) { + activeTasks.decrementAndGet(); + return; + } + + int idx = connectionIndex.getAndIncrement() % nettyStreamBootstraps.size(); + nettyStreamBootstraps.get(idx).open().addListener(future -> { + if (!future.isSuccess()) { + errors.incrementAndGet(); + firstError.compareAndSet(null, future.cause()); + if (!running.get()) + activeTasks.decrementAndGet(); + else + makeRequest[0].run(); + return; + } + + Http2StreamChannel streamChannel = (Http2StreamChannel) future.get(); + streamChannel.pipeline().addLast(new SimpleChannelInboundHandler() { + private final byte[] copyBuf = new byte[8192]; + + @Override + protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame frame) { + if (frame instanceof Http2DataFrame df) { + // Copy data like Smithy does, not just skip + var buf = df.content(); + while (buf.readableBytes() > 0) { + int toRead = Math.min(buf.readableBytes(), copyBuf.length); + buf.readBytes(copyBuf, 0, toRead); + } + } + boolean endStream = (frame instanceof Http2HeadersFrame hf && hf.isEndStream()) + || (frame instanceof Http2DataFrame df2 && df2.isEndStream()); + if (endStream) { + requests.incrementAndGet(); + ctx.close(); + makeRequest[0].run(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + errors.incrementAndGet(); + firstError.compareAndSet(null, cause); + ctx.close(); + makeRequest[0].run(); + } + }); + + streamChannel.writeAndFlush(new io.netty.handler.codec.http2.DefaultHttp2HeadersFrame(headers, true)); + }); + }; + + for (int i = 0; i < concurrency; i++) { + makeRequest[0].run(); + } + + Thread.sleep(1000); + running.set(false); + + // Don't wait long - just let in-flight requests drain briefly + long deadline = System.currentTimeMillis() + 100; + while (activeTasks.get() > 0 && System.currentTimeMillis() < deadline) { + Thread.sleep(10); + } + + counter.requests = requests.get(); + counter.errors = errors.get(); + counter.firstError = firstError.get(); + System.out.println("Netty H2c GET 1MB: " + counter.requests + " requests, " + counter.errors + " errors"); + counter.logErrors("Netty H2c GET 1MB"); + } +} diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java deleted file mode 100644 index 364715b6b..000000000 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/VirtualThreadScalingBenchmark.java +++ /dev/null @@ -1,842 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client; - -import io.helidon.common.tls.Tls; -import io.helidon.webclient.api.HttpClientResponse; -import io.helidon.webclient.api.WebClient; -import io.helidon.webclient.http2.Http2Client; -import io.netty.bootstrap.Bootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.codec.http2.DefaultHttp2Headers; -import io.netty.handler.codec.http2.Http2FrameCodecBuilder; -import io.netty.handler.codec.http2.Http2HeadersFrame; -import io.netty.handler.codec.http2.Http2MultiplexHandler; -import io.netty.handler.codec.http2.Http2StreamChannel; -import io.netty.handler.codec.http2.Http2StreamChannelBootstrap; -import io.netty.handler.codec.http2.Http2StreamFrame; -import java.io.IOException; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.security.SecureRandom; -import java.security.cert.X509Certificate; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.config.ConnectionConfig; -import org.apache.hc.client5.http.config.RequestConfig; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; -import org.apache.hc.client5.http.impl.classic.HttpClients; -import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; -import org.apache.hc.core5.http.io.entity.EntityUtils; -import org.apache.hc.core5.util.Timeout; -import org.openjdk.jmh.annotations.AuxCounters; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Level; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Param; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.TearDown; -import org.openjdk.jmh.annotations.Threads; -import org.openjdk.jmh.annotations.Warmup; -import software.amazon.smithy.java.http.api.HttpRequest; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.dns.DnsResolver; - -/** - * Benchmark comparing HTTP/1.1 vs HTTP/2 clients at extreme concurrency. - * - *

      Uses an external Netty server (separate process) to get clean flame graphs - * without server code polluting the profile. - * - *

      Run with external server (recommended for profiling): - *

      - * ./gradlew :http:http-client:jmhWithServer
      - * 
      - * - *

      Or manually: - *

      - * # Terminal 1: Start server
      - * ./gradlew :http:http-client:startBenchmarkServer
      - *
      - * # Terminal 2: Run benchmark
      - * ./gradlew :http:http-client:jmh --args='-p externalServer=http://localhost:PORT'
      - *
      - * # Terminal 1: Stop server when done
      - * ./gradlew :http:http-client:stopBenchmarkServer
      - * 
      - */ -@BenchmarkMode(Mode.Throughput) -@OutputTimeUnit(TimeUnit.SECONDS) -@Warmup(iterations = 3, time = 4) -@Measurement(iterations = 3, time = 5) -@Fork(value = 1, jvmArgs = {"-Xms2g", "-Xmx2g"}) -@State(Scope.Benchmark) -public class VirtualThreadScalingBenchmark { - - // Note: this takes _forever_, so comment out whatever you don't want to try if you want it to go faster. - @Param({ - //"100", "1000", "5000", "10000", "20000", - "30000"}) - private int concurrency; - - @Param({//"1", "2", "3", "4", "5", "10", - "20", - //"50" - }) - private int connectionLimit; - - @Param({ - // "100", "1024", "2048", - "4096" - // , "8192" - }) - private int streamsLimit; - - // Server URL - use jmhWithServer task or set manually - // For h2c: "http://localhost:18081" (default) - // For h2 TLS: "https://localhost:18443" - // For h1: "http://localhost:18080" - @Param({"http://localhost:18081"}) - private String externalServer; - - // HTTP/1.1 clients - private HttpClient smithyClientH1; - private CloseableHttpClient apacheClientH1; - private WebClient helidonClientH1; - - // HTTP/2 cleartext clients (h2c) - private HttpClient smithyClientH2c; - private Http2Client helidonClientH2c; - - // HTTP/2 TLS clients (h2) - private HttpClient smithyClientH2; - private Http2Client helidonClientH2; - - // Netty HTTP/2 client (raw, no abstractions) - private EventLoopGroup nettyEventLoopGroup; - private Bootstrap nettyBootstrap; - private String nettyH2cHost; - private int nettyH2cPort; - - // Server URLs - private String h1BaseUrl; - private String h2BaseUrl; - private String h2cBaseUrl; - - // SSL context for clients - private SSLContext trustAllSslContext; - - @Setup - public void setup() throws Exception { - if (externalServer == null || externalServer.isEmpty()) { - throw new IllegalStateException( - "externalServer parameter is required. Use ./gradlew :http:http-client:jmhWithServer " + - "or set -p externalServer=http://localhost:PORT"); - } - - // Fixed server ports (must match BenchmarkServer.java) - // externalServer is used as the base host, ports are fixed - String host = externalServer.replaceAll("https?://", "").replaceAll(":\\d+.*", ""); - h1BaseUrl = "http://" + host + ":18080"; - h2BaseUrl = "https://" + host + ":18443"; - h2cBaseUrl = "http://" + host + ":18081"; - - System.out.println("Using external server: " + externalServer); - - // Create trust-all SSL context for self-signed cert - trustAllSslContext = createTrustAllSslContext(); - - // Static DNS resolver to bypass DNS overhead - DnsResolver staticDns = DnsResolver.staticMapping(Map.of( - "localhost", - List.of(InetAddress.getLoopbackAddress()))); - - // ===== HTTP/1.1 Clients ===== - - // Smithy H1 client - smithyClientH1 = HttpClient.builder() - .connectionPool(HttpConnectionPool.builder() - .maxConnectionsPerRoute(100) - .maxTotalConnections(100) - .maxIdleTime(Duration.ofMinutes(2)) - .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) - .dnsResolver(staticDns) - .build()) - .build(); - - // Apache H1 client (Apache HttpClient 5 classic API only supports H1) - PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); - connectionManager.setMaxTotal(5000); - connectionManager.setDefaultMaxPerRoute(5000); - connectionManager.setDefaultConnectionConfig(ConnectionConfig.custom() - .setConnectTimeout(Timeout.ofSeconds(10)) - .setSocketTimeout(Timeout.ofSeconds(30)) - .build()); - - apacheClientH1 = HttpClients.custom() - .setConnectionManager(connectionManager) - .setDefaultRequestConfig(RequestConfig.custom() - .setConnectionRequestTimeout(Timeout.ofSeconds(5)) - .build()) - .build(); - - // Helidon H1 client - helidonClientH1 = WebClient.builder() - .baseUri(h1BaseUrl) - .shareConnectionCache(false) - .connectionCacheSize(5000) - .build(); - - // ===== HTTP/2 Cleartext Clients (h2c) ===== - - // Smithy H2c client - created per iteration in setupIteration() - - // Helidon H2c client (cleartext, prior knowledge) - helidonClientH2c = Http2Client.builder() - .baseUri(h2cBaseUrl) - .shareConnectionCache(false) - .protocolConfig(pc -> pc.priorKnowledge(true)) - .build(); - - // ===== HTTP/2 TLS Clients (h2 with ALPN) ===== - - // Smithy H2 client (TLS with ALPN) - smithyClientH2 = HttpClient.builder() - .connectionPool(HttpConnectionPool.builder() - .maxConnectionsPerRoute(10000) - .maxTotalConnections(10000) - .maxIdleTime(Duration.ofMinutes(2)) - .dnsResolver(staticDns) - .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_2) - .sslContext(trustAllSslContext) - .h2StreamsPerConnection(100) - .build()) - .build(); - - // Helidon H2 client (TLS with ALPN) - // priorKnowledge(false) = use ALPN for protocol negotiation over TLS - // shareConnectionCache(true) is essential for HTTP/2 multiplexing - helidonClientH2 = Http2Client.builder() - .baseUri(h2BaseUrl) - .shareConnectionCache(true) - .tls(Tls.builder().trustAll(true).build()) - .protocolConfig(pc -> pc.priorKnowledge(false)) - .build(); - - // ===== Netty HTTP/2 Client (raw, no abstractions) ===== - nettyEventLoopGroup = new NioEventLoopGroup(); - nettyBootstrap = new Bootstrap(); - nettyBootstrap.group(nettyEventLoopGroup) - .channel(NioSocketChannel.class) - .option(ChannelOption.SO_KEEPALIVE, true) - .handler(new ChannelInitializer() { - @Override - protected void initChannel(SocketChannel ch) { - ch.pipeline() - .addLast( - Http2FrameCodecBuilder.forClient() - .initialSettings( - io.netty.handler.codec.http2.Http2Settings.defaultSettings() - .maxConcurrentStreams(10000)) - .build(), - new Http2MultiplexHandler(new SimpleChannelInboundHandler() { - @Override - protected void channelRead0( - ChannelHandlerContext ctx, - Http2StreamFrame msg - ) { - // Inbound stream handler (for server push, not used here) - } - })); - } - }); - - // Store host/port for connection during benchmark (not pre-connected) - nettyH2cHost = h2cBaseUrl.replaceAll("https?://", "").replaceAll(":\\d+.*", ""); - nettyH2cPort = 18081; - } - - private SSLContext createTrustAllSslContext() throws Exception { - TrustManager[] trustAllCerts = new TrustManager[] { - new X509TrustManager() { - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - - public void checkClientTrusted(X509Certificate[] certs, String authType) {} - - public void checkServerTrusted(X509Certificate[] certs, String authType) {} - } - }; - - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, trustAllCerts, new SecureRandom()); - return sslContext; - } - - @TearDown - public void teardown() throws Exception { - // Close H1 clients - if (smithyClientH1 != null) { - smithyClientH1.close(); - } - if (apacheClientH1 != null) { - apacheClientH1.close(); - } - if (helidonClientH1 != null) { - helidonClientH1.closeResource(); - } - - // Close H2c clients - // smithyClientH2c closed per-iteration in setupIteration() - if (helidonClientH2c != null) { - helidonClientH2c.closeResource(); - } - - // Close H2 TLS clients - if (smithyClientH2 != null) { - smithyClientH2.close(); - } - if (helidonClientH2 != null) { - helidonClientH2.closeResource(); - } - - // Close Netty event loop group - if (nettyEventLoopGroup != null) { - nettyEventLoopGroup.shutdownGracefully().sync(); - } - } - - @AuxCounters(AuxCounters.Type.EVENTS) - @State(Scope.Thread) - public static class RequestCounter { - public long requests; - public long errors; - - @Setup(Level.Iteration) - public void reset() { - requests = 0; - errors = 0; - } - } - - @Setup(Level.Iteration) - public void setupIteration() throws Exception { - if (smithyClientH2c != null) { - smithyClientH2c.close(); - } - - DnsResolver staticDns = DnsResolver.staticMapping(Map.of( - "localhost", - List.of(InetAddress.getLoopbackAddress()))); - - System.out.println("Creating client: connectionLimit=" + connectionLimit + ", streamsLimit=" + streamsLimit); - - smithyClientH2c = HttpClient.builder() - .connectionPool(HttpConnectionPool.builder() - .maxConnectionsPerRoute(connectionLimit) - .h2StreamsPerConnection(streamsLimit) - .maxIdleTime(Duration.ofMinutes(2)) - .dnsResolver(staticDns) - .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) - .build()) - .build(); - - try (var res = smithyClientH2c.send(HttpRequest.builder() - .uri(URI.create(h2cBaseUrl + "/reset")) - .method("POST") - .build())) { - res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); - } - Thread.sleep(100); - } - - @TearDown(Level.Iteration) - public void teardownIteration() throws Exception { - try (var res = smithyClientH2c.send(HttpRequest.builder() - .uri(URI.create(h2cBaseUrl + "/stats")) - .method("GET") - .build())) { - String stats = - new String(res.body().asInputStream().readAllBytes(), StandardCharsets.UTF_8); - System.out.println("Server stats [connectionLimit=" + connectionLimit + ", streamsLimit=" + streamsLimit - + "]: " + stats); - } - } - - // ===== HTTP/1.1 Benchmarks ===== - - /** - * Smithy HTTP/1.1 client throughput with virtual threads. - */ - @Benchmark - @Threads(1) - public void smithyH1(RequestCounter counter) throws InterruptedException { - var requests = new AtomicLong(); - var errors = new AtomicLong(); - AtomicReference firstError = new AtomicReference<>(); - var running = new AtomicBoolean(true); - var latch = new CountDownLatch(concurrency); - var uri = URI.create(h1BaseUrl + "/get"); - - try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { - for (int i = 0; i < concurrency; i++) { - executor.submit(() -> { - try { - while (running.get()) { - // Close will drain the remaining bytes - smithyClientH1.send(HttpRequest.builder().uri(uri).method("GET").build()).close(); - requests.incrementAndGet(); - } - } catch (IOException e) { - errors.incrementAndGet(); - firstError.compareAndSet(null, e); - } finally { - latch.countDown(); - } - }); - } - - Thread.sleep(1000); - running.set(false); - var _ignored = latch.await(5, TimeUnit.SECONDS); - } - - counter.requests = requests.get(); - counter.errors = errors.get(); - - // Log first error for debugging - if (firstError.get() != null) { - System.err.println("errors: " + errors.get() + ", first error:"); - firstError.get().printStackTrace(System.err); - } - } - - /** - * Apache HTTP/1.1 client throughput with virtual threads. - * Note: Apache HttpClient 5 classic API only supports HTTP/1.1. - */ - @Benchmark - @Threads(1) - public void apacheH1(RequestCounter counter) throws InterruptedException { - var requests = new AtomicLong(); - var errors = new AtomicLong(); - var running = new AtomicBoolean(true); - var latch = new CountDownLatch(concurrency); - var target = h1BaseUrl + "/get"; - - try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { - for (int i = 0; i < concurrency; i++) { - executor.submit(() -> { - try { - while (running.get()) { - HttpGet request = new HttpGet(target); - try (CloseableHttpResponse response = apacheClientH1.execute(request)) { - EntityUtils.consume(response.getEntity()); - } - requests.incrementAndGet(); - } - } catch (Exception ignored) { - errors.incrementAndGet(); - } finally { - latch.countDown(); - } - }); - } - - Thread.sleep(1000); - running.set(false); - var _ignored = latch.await(5, TimeUnit.SECONDS); - } - - counter.requests = requests.get(); - counter.errors = errors.get(); - } - - /** - * Helidon HTTP/1.1 client throughput with virtual threads. - */ - @Benchmark - @Threads(1) - public void helidonH1(RequestCounter counter) throws InterruptedException { - var requests = new AtomicLong(); - var errors = new AtomicLong(); - var running = new AtomicBoolean(true); - var latch = new CountDownLatch(concurrency); - - try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { - for (int i = 0; i < concurrency; i++) { - executor.submit(() -> { - try { - while (running.get()) { - try (HttpClientResponse response = helidonClientH1.get("/get").request()) { - response.entity().consume(); - } - requests.incrementAndGet(); - } - } catch (Exception e) { - errors.incrementAndGet(); - } finally { - latch.countDown(); - } - }); - } - - Thread.sleep(1000); - running.set(false); - var _ignored = latch.await(5, TimeUnit.SECONDS); - } - - counter.requests = requests.get(); - counter.errors = errors.get(); - } - - // ===== HTTP/2c Benchmarks ===== - - @Benchmark - @Threads(1) - public void smithyH2c(RequestCounter counter) throws InterruptedException { - var requests = new AtomicLong(); - var errors = new AtomicLong(); - AtomicReference firstError = new AtomicReference<>(); - var running = new AtomicBoolean(true); - var latch = new CountDownLatch(concurrency); - var uri = URI.create(h2cBaseUrl + "/get"); - var request = HttpRequest.builder().uri(uri).method("GET").build(); - - try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { - for (int i = 0; i < concurrency; i++) { - executor.submit(() -> { - try { - while (running.get()) { - try (var res = smithyClientH2c.send(request)) { - res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); - requests.incrementAndGet(); - } - } - } catch (Exception e) { - errors.incrementAndGet(); - firstError.compareAndSet(null, e); - } finally { - latch.countDown(); - } - }); - } - - Thread.sleep(1000); - running.set(false); - var _ignored = latch.await(5, TimeUnit.SECONDS); - } - - counter.requests = requests.get(); - counter.errors = errors.get(); - - // Log first error for debugging - if (firstError.get() != null) { - System.err.println("errors: " + errors.get() + ", first error:"); - firstError.get().printStackTrace(System.err); - } - } - - @Benchmark - @Threads(1) - public void helidonH2c(RequestCounter counter) throws InterruptedException { - var requests = new AtomicLong(); - var errors = new AtomicLong(); - var firstError = new AtomicReference(); - var running = new AtomicBoolean(true); - var latch = new CountDownLatch(concurrency); - - try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { - for (int i = 0; i < concurrency; i++) { - executor.submit(() -> { - try { - while (running.get()) { - try (HttpClientResponse response = helidonClientH2c.get("/get").request()) { - response.entity().consume(); - } - requests.incrementAndGet(); - } - } catch (Exception e) { - errors.incrementAndGet(); - firstError.compareAndSet(null, e); - } finally { - latch.countDown(); - } - }); - } - - Thread.sleep(1000); - running.set(false); - var _ignored = latch.await(5, TimeUnit.SECONDS); - } - - counter.requests = requests.get(); - counter.errors = errors.get(); - - // Log first error for debugging - if (firstError.get() != null) { - System.err.println("Helidon H2c errors: " + errors.get() + ", first error:"); - firstError.get().printStackTrace(System.err); - } - } - - /** - * Raw Netty HTTP/2 client throughput with single connection. - */ - @Benchmark - @Threads(1) - public void nettyH2c(RequestCounter counter) throws InterruptedException { - runNettyH2c(1, counter); - } - - /** - * Raw Netty HTTP/2 client throughput with pooled connections. - */ - @Benchmark - @Threads(1) - public void nettyH2cPooled(RequestCounter counter) throws InterruptedException { - runNettyH2c(3, counter); - } - - private void runNettyH2c(int numConnections, RequestCounter counter) throws InterruptedException { - var requests = new AtomicLong(); - var errors = new AtomicLong(); - var firstError = new AtomicReference(); - var running = new AtomicBoolean(true); - var activeTasks = new AtomicLong(concurrency); - - // Connect to h2c server during benchmark - List channels = new ArrayList<>(); - try { - for (int i = 0; i < numConnections; i++) { - channels.add( - nettyBootstrap.connect(new InetSocketAddress(nettyH2cHost, nettyH2cPort)).sync().channel()); - } - } catch (Exception e) { - counter.errors = 1; - return; - } - - try { - // Create stream bootstraps for each connection - List streamBootstraps = channels.stream() - .map(Http2StreamChannelBootstrap::new) - .toList(); - - // Pre-create headers - DefaultHttp2Headers headers = new DefaultHttp2Headers(); - headers.method("GET"); - headers.path("/get"); - headers.scheme("http"); - headers.authority("localhost:18081"); - - // Handler that opens a new stream, sends request, and repeats on response - Runnable[] makeRequest = new Runnable[1]; - var connectionIndex = new AtomicInteger(0); - makeRequest[0] = () -> { - if (!running.get()) { - activeTasks.decrementAndGet(); - return; - } - - // Round-robin across connections - int idx = connectionIndex.getAndIncrement() % streamBootstraps.size(); - Http2StreamChannelBootstrap streamBootstrap = streamBootstraps.get(idx); - - streamBootstrap.open().addListener(future -> { - if (!future.isSuccess()) { - errors.incrementAndGet(); - firstError.compareAndSet(null, future.cause()); - if (!running.get()) { - activeTasks.decrementAndGet(); - } else { - makeRequest[0].run(); - } - return; - } - - Http2StreamChannel streamChannel = (Http2StreamChannel) future.get(); - streamChannel.pipeline().addLast(new SimpleChannelInboundHandler() { - @Override - protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame frame) { - if (frame instanceof io.netty.handler.codec.http2.Http2DataFrame dataFrame) { - dataFrame.content().readableBytes(); - } - - boolean endStream = false; - if (frame instanceof Http2HeadersFrame headersFrame) { - endStream = headersFrame.isEndStream(); - } else if (frame instanceof io.netty.handler.codec.http2.Http2DataFrame dataFrame) { - endStream = dataFrame.isEndStream(); - } - - if (endStream) { - requests.incrementAndGet(); - ctx.close(); - makeRequest[0].run(); - } - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - errors.incrementAndGet(); - firstError.compareAndSet(null, cause); - ctx.close(); - makeRequest[0].run(); - } - }); - - streamChannel - .writeAndFlush(new io.netty.handler.codec.http2.DefaultHttp2HeadersFrame(headers, true)); - }); - }; - - for (int i = 0; i < concurrency; i++) { - makeRequest[0].run(); - } - - Thread.sleep(1000); - running.set(false); - - long deadline = System.currentTimeMillis() + 5000; - while (activeTasks.get() > 0 && System.currentTimeMillis() < deadline) { - Thread.sleep(10); - } - } finally { - for (Channel ch : channels) { - ch.close().sync(); - } - } - - counter.requests = requests.get(); - counter.errors = errors.get(); - - if (firstError.get() != null) { - System.err.println("Netty H2c errors: " + errors.get() + ", first error:"); - firstError.get().printStackTrace(System.err); - } - } - - // ===== HTTP/2 TLS Benchmarks ===== - - @Benchmark - @Threads(1) - public void smithyH2(RequestCounter counter) throws InterruptedException { - var requests = new AtomicLong(); - var errors = new AtomicLong(); - var firstError = new AtomicReference(); - var running = new AtomicBoolean(true); - var latch = new CountDownLatch(concurrency); - var uri = URI.create(h2BaseUrl + "/get"); - - try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { - for (int i = 0; i < concurrency; i++) { - executor.submit(() -> { - try { - while (running.get()) { - smithyClientH2.send(HttpRequest.builder().uri(uri).method("GET").build()); - requests.incrementAndGet(); - } - } catch (Exception e) { - errors.incrementAndGet(); - firstError.compareAndSet(null, e); - } finally { - latch.countDown(); - } - }); - } - - Thread.sleep(1000); - running.set(false); - var _ignored = latch.await(5, TimeUnit.SECONDS); - } - - counter.requests = requests.get(); - counter.errors = errors.get(); - - // Log first error for debugging - if (firstError.get() != null) { - System.err.println("H2 TLS errors: " + errors.get() + ", first error:"); - firstError.get().printStackTrace(System.err); - } - } - - @Benchmark - @Threads(1) - public void helidonH2(RequestCounter counter) throws InterruptedException { - var requests = new AtomicLong(); - var errors = new AtomicLong(); - var firstError = new AtomicReference(); - var running = new AtomicBoolean(true); - var latch = new CountDownLatch(concurrency); - - try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { - for (int i = 0; i < concurrency; i++) { - executor.submit(() -> { - try { - while (running.get()) { - try (HttpClientResponse response = helidonClientH2.get("/get").request()) { - response.entity().consume(); - } - requests.incrementAndGet(); - } - } catch (Exception e) { - errors.incrementAndGet(); - firstError.compareAndSet(null, e); - } finally { - latch.countDown(); - } - }); - } - - Thread.sleep(1000); - running.set(false); - var _ignored = latch.await(5, TimeUnit.SECONDS); - } - - counter.requests = requests.get(); - counter.errors = errors.get(); - - // Log first error for debugging - if (firstError.get() != null) { - System.err.println("Helidon H2 TLS errors: " + errors.get() + ", first error:"); - firstError.get().printStackTrace(System.err); - } - } -} diff --git a/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java index 9df22f60b..f1116eaa2 100644 --- a/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java +++ b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java @@ -74,6 +74,7 @@ public final class BenchmarkServer { private static final byte[] CONTENT = "{\"status\":\"ok\"}".getBytes(StandardCharsets.UTF_8); + private static final byte[] MB_CONTENT = new byte[1024 * 1024]; // 1MB for large transfer tests // Fixed ports for benchmark server (avoids dynamic port discovery complexity) public static final int DEFAULT_H1_PORT = 18080; @@ -240,14 +241,30 @@ public void shutdown() throws InterruptedException { private static class Http1RequestHandler extends SimpleChannelInboundHandler { @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) { - FullHttpResponse response = new DefaultFullHttpResponse( - HTTP_1_1, - OK, - Unpooled.wrappedBuffer(CONTENT)); - response.headers() - .set(CONTENT_TYPE, "application/json") - .set(CONNECTION, KEEP_ALIVE) - .setInt(CONTENT_LENGTH, CONTENT.length); + String uri = msg.uri(); + FullHttpResponse response; + + if (uri.startsWith("/post") || uri.startsWith("/putmb")) { + // POST/PUT returns empty 200 OK + response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.EMPTY_BUFFER); + response.headers() + .set(CONNECTION, KEEP_ALIVE) + .setInt(CONTENT_LENGTH, 0); + } else if (uri.startsWith("/getmb")) { + // Return 1MB response + response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(MB_CONTENT)); + response.headers() + .set(CONTENT_TYPE, "application/octet-stream") + .set(CONNECTION, KEEP_ALIVE) + .setInt(CONTENT_LENGTH, MB_CONTENT.length); + } else { + // GET returns JSON body + response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(CONTENT)); + response.headers() + .set(CONTENT_TYPE, "application/json") + .set(CONNECTION, KEEP_ALIVE) + .setInt(CONTENT_LENGTH, CONTENT.length); + } ctx.writeAndFlush(response); } @@ -297,6 +314,13 @@ private static class Http2RequestHandler extends ChannelInboundHandlerAdapter { .status("200") .set("content-type", "application/json") .setInt("content-length", CONTENT.length); + private static final Http2Headers EMPTY_RESPONSE_HEADERS = new DefaultHttp2Headers(true, 2) + .status("200") + .setInt("content-length", 0); + private static final Http2Headers MB_RESPONSE_HEADERS = new DefaultHttp2Headers(true, 3) + .status("200") + .set("content-type", "application/octet-stream") + .setInt("content-length", MB_CONTENT.length); private static final java.util.concurrent.atomic.AtomicInteger connectionCount = new java.util.concurrent.atomic.AtomicInteger(); @@ -347,6 +371,24 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { ctx.write(new DefaultHttp2HeadersFrame(statsHeaders, false).stream(headersFrame.stream())); ctx.writeAndFlush(new DefaultHttp2DataFrame(Unpooled.wrappedBuffer(body), true) .stream(headersFrame.stream())); + } else if ("/post".contentEquals(path) || "/putmb".contentEquals(path)) { + // POST/PUT with body - wait for data frames, then send empty 200 + if (headersFrame.isEndStream()) { + // No body, respond immediately + var counter = streamCounts.get(ctx.channel().id().asShortText()); + if (counter != null) + counter.incrementAndGet(); + sendEmptyResponse(ctx, headersFrame.stream()); + } + // else: wait for DATA frames + } else if ("/getmb".contentEquals(path)) { + // Return 1MB response + if (headersFrame.isEndStream()) { + var counter = streamCounts.get(ctx.channel().id().asShortText()); + if (counter != null) + counter.incrementAndGet(); + sendMbResponse(ctx, headersFrame.stream()); + } } else if (headersFrame.isEndStream()) { var counter = streamCounts.get(ctx.channel().id().asShortText()); if (counter != null) @@ -359,7 +401,8 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { var counter = streamCounts.get(ctx.channel().id().asShortText()); if (counter != null) counter.incrementAndGet(); - sendResponse(ctx, dataFrame.stream()); + // POST requests with body get empty response + sendEmptyResponse(ctx, dataFrame.stream()); } } } @@ -371,6 +414,17 @@ private void sendResponse(ChannelHandlerContext ctx, Http2FrameStream stream) { true).stream(stream)); } + private void sendEmptyResponse(ChannelHandlerContext ctx, Http2FrameStream stream) { + ctx.writeAndFlush(new DefaultHttp2HeadersFrame(EMPTY_RESPONSE_HEADERS, true).stream(stream)); + } + + private void sendMbResponse(ChannelHandlerContext ctx, Http2FrameStream stream) { + ctx.write(new DefaultHttp2HeadersFrame(MB_RESPONSE_HEADERS, false).stream(stream)); + ctx.writeAndFlush(new DefaultHttp2DataFrame( + Unpooled.wrappedBuffer(MB_CONTENT), + true).stream(stream)); + } + @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { ctx.close(); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java index a0078de40..8d596a99c 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java @@ -42,6 +42,7 @@ final class HttpConnectionFactory { private final HttpVersionPolicy versionPolicy; private final DnsResolver dnsResolver; private final HttpSocketFactory socketFactory; + private final int h2InitialWindowSize; HttpConnectionFactory( Duration connectTimeout, @@ -52,7 +53,8 @@ final class HttpConnectionFactory { SSLParameters sslParameters, HttpVersionPolicy versionPolicy, DnsResolver dnsResolver, - HttpSocketFactory socketFactory + HttpSocketFactory socketFactory, + int h2InitialWindowSize ) { this.connectTimeout = connectTimeout; this.tlsNegotiationTimeout = tlsNegotiationTimeout; @@ -63,6 +65,7 @@ final class HttpConnectionFactory { this.versionPolicy = versionPolicy; this.dnsResolver = dnsResolver; this.socketFactory = socketFactory; + this.h2InitialWindowSize = h2InitialWindowSize; } /** @@ -175,7 +178,7 @@ private HttpConnection createProtocolConnection(Socket socket, Route route) thro try { if ("h2".equals(protocol) || "h2c".equals(protocol)) { - return new H2Connection(socket, route, readTimeout, writeTimeout); + return new H2Connection(socket, route, readTimeout, writeTimeout, h2InitialWindowSize); } else { return new H1Connection(socket, route, readTimeout); } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java index de01fb5de..d90c5a32c 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java @@ -179,7 +179,8 @@ public final class HttpConnectionPool implements ConnectionPool { builder.sslParameters, builder.versionPolicy, dnsResolver, - builder.socketFactory); + builder.socketFactory, + builder.h2InitialWindowSize); this.h1Manager = new H1ConnectionManager(this.maxIdleTimeNanos); this.connectionPermits = new Semaphore(builder.maxTotalConnections, false); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java index 4cee9c220..89d1a5849 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java @@ -24,6 +24,7 @@ public final class HttpConnectionPoolBuilder { int maxTotalConnections = 256; int maxConnectionsPerRoute = 20; int h2StreamsPerConnection = 100; + int h2InitialWindowSize = 65535; // RFC 9113 default final Map perHostLimits = new HashMap<>(); Duration maxIdleTime = Duration.ofMinutes(2); @@ -378,6 +379,36 @@ public HttpConnectionPoolBuilder socketFactory(HttpSocketFactory socketFactory) return this; } + /** + * Set HTTP/2 initial window size for flow control (default: 65535 bytes). + * + *

      This controls the initial flow control window size advertised to the server + * for both connection-level and stream-level flow control. Larger values allow + * more data to be sent before waiting for WINDOW_UPDATE frames, which improves + * throughput for large payloads. + * + *

      Performance considerations: + *

        + *
      • Default (65535): RFC 9113 default, conservative memory usage
      • + *
      • 1MB (1048576): Good for large response bodies, reduces WINDOW_UPDATE overhead
      • + *
      • Higher values: Better throughput but more memory per stream
      • + *
      + * + *

      For workloads with large response bodies (e.g., file downloads, large API responses), + * consider setting this to 1MB or higher to reduce flow control overhead. + * + * @param windowSize initial window size in bytes, must be between 1 and 2^31-1 + * @return this builder + * @throws IllegalArgumentException if windowSize is not in valid range + */ + public HttpConnectionPoolBuilder h2InitialWindowSize(int windowSize) { + if (windowSize <= 0) { + throw new IllegalArgumentException("h2InitialWindowSize must be positive: " + windowSize); + } + this.h2InitialWindowSize = windowSize; + return this; + } + /** * Set maximum concurrent streams per HTTP/2 connection before creating a new connection (default: 100). * diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java index 6c24106e8..9818f969d 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java @@ -108,7 +108,8 @@ private enum State { private volatile int remoteMaxHeaderListSize = Integer.MAX_VALUE; // Connection receive window (send window is managed by muxer). Only accessed by reader thread. - private int connectionRecvWindow = DEFAULT_INITIAL_WINDOW_SIZE; + private int connectionRecvWindow; + private final int initialWindowSize; // Connection state private volatile State state = State.CONNECTED; @@ -121,8 +122,15 @@ private enum State { /** * Create an HTTP/2 connection from a connected socket. + * + * @param socket the connected socket + * @param route the route for this connection + * @param readTimeout read timeout duration + * @param writeTimeout write timeout duration + * @param initialWindowSize initial flow control window size in bytes */ - public H2Connection(Socket socket, Route route, Duration readTimeout, Duration writeTimeout) throws IOException { + public H2Connection(Socket socket, Route route, Duration readTimeout, Duration writeTimeout, int initialWindowSize) + throws IOException { this.socket = socket; var socketIn = new UnsyncBufferedInputStream(socket.getInputStream(), 8192); this.socketOut = new UnsyncBufferedOutputStream(socket.getOutputStream(), 8192); @@ -131,9 +139,15 @@ public H2Connection(Socket socket, Route route, Duration readTimeout, Duration w this.writeTimeoutMs = writeTimeout.toMillis(); this.frameCodec = new H2FrameCodec(socketIn, socketOut, DEFAULT_MAX_FRAME_SIZE); this.hpackDecoder = new HpackDecoder(DEFAULT_HEADER_TABLE_SIZE); + this.initialWindowSize = initialWindowSize; + this.connectionRecvWindow = initialWindowSize; // Create muxer before connection preface (applyRemoteSettings needs it) - this.muxer = new H2Muxer(this, frameCodec, DEFAULT_HEADER_TABLE_SIZE, "h2-writer-" + route.host()); + this.muxer = new H2Muxer(this, + frameCodec, + DEFAULT_HEADER_TABLE_SIZE, + "h2-writer-" + route.host(), + initialWindowSize); // Perform connection preface try { @@ -378,12 +392,20 @@ private void sendConnectionPreface() throws IOException { SETTINGS_MAX_CONCURRENT_STREAMS, 100, SETTINGS_INITIAL_WINDOW_SIZE, - 65535, + initialWindowSize, SETTINGS_MAX_FRAME_SIZE, 16384, SETTINGS_ENABLE_PUSH, 0); frameCodec.flush(); + + // If using a larger window than the RFC default, send a connection-level WINDOW_UPDATE + // to expand the connection receive window immediately + if (initialWindowSize > DEFAULT_INITIAL_WINDOW_SIZE) { + int increment = initialWindowSize - DEFAULT_INITIAL_WINDOW_SIZE; + frameCodec.writeWindowUpdate(0, increment); + frameCodec.flush(); + } } private void receiveServerPreface() throws IOException { @@ -710,8 +732,8 @@ List decodeHeaders(byte[] headerBlock) throws IOException { // Called only from reader thread - no synchronization needed void consumeConnectionRecvWindow(int bytes) throws IOException { connectionRecvWindow -= bytes; - if (connectionRecvWindow < DEFAULT_INITIAL_WINDOW_SIZE / 2) { - int increment = DEFAULT_INITIAL_WINDOW_SIZE - connectionRecvWindow; + if (connectionRecvWindow < initialWindowSize / 2) { + int increment = initialWindowSize - connectionRecvWindow; connectionRecvWindow += increment; muxer.queueControlFrame(0, H2Muxer.ControlFrameType.WINDOW_UPDATE, diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index bdd38db7a..49d834ff8 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.http.client.h2; -import static software.amazon.smithy.java.http.client.h2.H2Constants.DEFAULT_INITIAL_WINDOW_SIZE; import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_CANCEL; import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_FLOW_CONTROL_ERROR; import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_PROTOCOL_ERROR; @@ -117,11 +116,12 @@ enum ReadState { // Stream state machine per RFC 9113 Section 5.1 private volatile StreamState streamState = StreamState.IDLE; - // Stream-level timeouts + // Stream-level timeouts (tick-based: 1 tick = TIMEOUT_POLL_INTERVAL_MS) private final long readTimeoutMs; private final long writeTimeoutMs; + private final int readTimeoutTicks; // Number of ticks before timeout (0 = no timeout) private final AtomicLong readSeq = new AtomicLong(); // Activity counter, incremented on read activity - private volatile long readDeadlineNanos; // 0 = no deadline, >0 = deadline in nanos + private volatile int readDeadlineTick; // 0 = no deadline, >0 = deadline tick private final AtomicBoolean readTimedOut = new AtomicBoolean(); // At-most-once timeout flag // Response state @@ -155,6 +155,7 @@ enum ReadState { // sendWindow: Semaphore-based, VT blocks naturally when exhausted (no lock needed) // streamRecvWindow: only accessed by application thread in readDataChunk() (single-threaded) private final FlowControlWindow sendWindow; + private final int initialWindowSize; private int streamRecvWindow; // === OUTBOUND PATH (VT → Writer) === @@ -175,15 +176,21 @@ enum ReadState { * @param request the HTTP request * @param readTimeoutMs timeout in milliseconds for waiting on response data * @param writeTimeoutMs timeout in milliseconds for waiting on flow control window + * @param initialWindowSize initial flow control window size for this stream */ - H2Exchange(H2Muxer muxer, HttpRequest request, long readTimeoutMs, long writeTimeoutMs) { + H2Exchange(H2Muxer muxer, HttpRequest request, long readTimeoutMs, long writeTimeoutMs, int initialWindowSize) { this.muxer = muxer; this.request = request; this.streamId = -1; // Will be set later this.readTimeoutMs = readTimeoutMs; this.writeTimeoutMs = writeTimeoutMs; + // Convert timeout to ticks: ceil(readTimeoutMs / pollIntervalMs) + this.readTimeoutTicks = readTimeoutMs <= 0 + ? 0 + : Math.max(1, (int) Math.ceil((double) readTimeoutMs / H2Muxer.TIMEOUT_POLL_INTERVAL_MS)); this.sendWindow = new FlowControlWindow(muxer.getRemoteInitialWindowSize()); - this.streamRecvWindow = DEFAULT_INITIAL_WINDOW_SIZE; + this.initialWindowSize = initialWindowSize; + this.streamRecvWindow = initialWindowSize; } /** @@ -208,10 +215,10 @@ long getReadTimeoutMs() { } /** - * Get read deadline in nanoseconds (0 = no deadline). + * Get read deadline tick (0 = no deadline). */ - long getReadDeadlineNanos() { - return readDeadlineNanos; + int getReadDeadlineTick() { + return readDeadlineTick; } /** @@ -232,11 +239,15 @@ boolean markReadTimedOut() { /** * Record read activity: bump sequence and reset deadline. * Called when headers or data arrive. + * + *

      Uses tick-based timeout: instead of calling System.nanoTime() (expensive), + * we read the current tick from the muxer (cheap volatile read) and compute + * the deadline as currentTick + timeoutTicks. */ private void onReadActivity() { - if (readTimeoutMs > 0) { + if (readTimeoutTicks > 0) { readSeq.incrementAndGet(); - readDeadlineNanos = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(readTimeoutMs); + readDeadlineTick = muxer.currentTimeoutTick() + readTimeoutTicks; } } @@ -244,7 +255,7 @@ private void onReadActivity() { * Clear read deadline (no timeout). */ private void clearReadDeadline() { - readDeadlineNanos = 0; + readDeadlineTick = 0; } /** @@ -406,7 +417,7 @@ boolean ensureBufferSpace(int requiredSpace) { // Lazy allocation on first DATA frame - borrow from connection pool if (dataBuffer == null) { int size = Math.max(INITIAL_BUFFER_SIZE, requiredSpace); - size = Math.min(size, DEFAULT_INITIAL_WINDOW_SIZE); + size = Math.min(size, initialWindowSize); dataBuffer = muxer.borrowBuffer(size); return true; } @@ -435,7 +446,7 @@ boolean ensureBufferSpace(int requiredSpace) { while (newSize < writePos + requiredSpace) { newSize *= 2; } - newSize = Math.min(newSize, DEFAULT_INITIAL_WINDOW_SIZE); + newSize = Math.min(newSize, initialWindowSize); if (writePos + requiredSpace > newSize) { return false; @@ -1068,8 +1079,8 @@ int availableInBuffer() { */ private void updateStreamRecvWindow(int bytesConsumed) throws IOException { streamRecvWindow -= bytesConsumed; - if (streamRecvWindow < DEFAULT_INITIAL_WINDOW_SIZE / 2) { - int increment = DEFAULT_INITIAL_WINDOW_SIZE - streamRecvWindow; + if (streamRecvWindow < initialWindowSize / 2) { + int increment = initialWindowSize - streamRecvWindow; // Queue stream-level WINDOW_UPDATE - writer thread will send it muxer.queueControlFrame(streamId, H2Muxer.ControlFrameType.WINDOW_UPDATE, increment, writeTimeoutMs); streamRecvWindow += increment; diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java index 37bf42495..5233b8242 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java @@ -127,7 +127,8 @@ enum ControlFrameType { "set-cookie"); // How often to check for read timeouts (every ~100ms) - private static final long READ_TIMOUT_FREQUENCY = TimeUnit.MILLISECONDS.toNanos(100); + // This is also the resolution of the tick-based timeout system + static final int TIMEOUT_POLL_INTERVAL_MS = 100; // Singleton wake-up signal private static final WorkItem.CheckDataQueue CHECK_DATA_QUEUE = new WorkItem.CheckDataQueue(); @@ -153,10 +154,14 @@ enum ControlFrameType { private volatile int goawayLastStreamId = Integer.MAX_VALUE; private volatile IOException writeError; + // Tick-based timeout: incremented every TIMEOUT_POLL_INTERVAL_MS by watchdog + private volatile int timeoutTick; + // === DEPENDENCIES === private final ConnectionCallback connectionCallback; private final H2FrameCodec frameCodec; private final BufferPool bufferPool; + private final int initialWindowSize; // === WORK QUEUES === private final BlockingQueue workQueue; @@ -178,11 +183,19 @@ enum ControlFrameType { * @param frameCodec the frame codec for writing * @param initialTableSize initial HPACK table size * @param threadName name for the writer thread + * @param initialWindowSize initial flow control window size */ - H2Muxer(ConnectionCallback connectionCallback, H2FrameCodec frameCodec, int initialTableSize, String threadName) { + H2Muxer( + ConnectionCallback connectionCallback, + H2FrameCodec frameCodec, + int initialTableSize, + String threadName, + int initialWindowSize + ) { this.connectionCallback = connectionCallback; this.frameCodec = frameCodec; - this.bufferPool = new BufferPool(32, DEFAULT_INITIAL_WINDOW_SIZE, DEFAULT_INITIAL_WINDOW_SIZE, 1024); + this.initialWindowSize = initialWindowSize; + this.bufferPool = new BufferPool(32, initialWindowSize, initialWindowSize, 1024); this.workQueue = new ArrayBlockingQueue<>(H2Constants.WRITER_QUEUE_CAPACITY); this.hpackEncoder = new HpackEncoder(initialTableSize); this.headerEncodeBuffer = new ByteBufferOutputStream(512); @@ -212,7 +225,7 @@ H2Exchange newExchange(HttpRequest request, long readTimeoutMs, long writeTimeou " (limit: " + remoteMaxConcurrentStreams + ")"); } - return new H2Exchange(this, request, readTimeoutMs, writeTimeoutMs); + return new H2Exchange(this, request, readTimeoutMs, writeTimeoutMs, initialWindowSize); } /** @@ -452,6 +465,18 @@ int getRemoteInitialWindowSize() { return remoteInitialWindowSize; } + int getInitialWindowSize() { + return initialWindowSize; + } + + /** + * Get the current timeout tick for deadline calculations. + * Called by exchanges when read activity occurs. + */ + int currentTimeoutTick() { + return timeoutTick; + } + void setMaxTableSize(int newSize) { this.pendingTableSizeUpdate = newSize; } @@ -467,26 +492,30 @@ IOException getWriteError() { * I/O already has inherent latency variance, and callers setting a "30s timeout" don't expect millisecond * precision. * + *

      Uses a tick-based system where the watchdog increments a global tick counter every poll interval. + * Exchanges track their deadline as a tick number rather than nanoseconds, eliminating System.nanoTime() + * calls from the hot path. + * *

      There is an unavoidable race: data could arrive just after we decide to timeout but before we signal. * We mitigate this by checking both deadline and activity sequence twice - we only timeout if the stream * appears expired and idle across two snapshots. The remaining race window is small and acceptable because * timeouts are approximate and failure is recoverable at the caller layer. */ - private void checkReadTimeouts(long nowNanos) { - streams.forEach(nowNanos, H2Muxer::checkExchangeTimeout); + private void checkReadTimeouts(int tick) { + streams.forEach(tick, H2Muxer::checkExchangeTimeout); } - private static void checkExchangeTimeout(H2Exchange exchange, long nowNanos) { + private static void checkExchangeTimeout(H2Exchange exchange, int nowTick) { long seq1 = exchange.getReadSeq(); - long d1 = exchange.getReadDeadlineNanos(); - if (d1 <= 0 || nowNanos <= d1) { + int d1 = exchange.getReadDeadlineTick(); + if (d1 <= 0 || nowTick < d1) { return; } // Second snapshot: did anything change while we were looking? long seq2 = exchange.getReadSeq(); - long d2 = exchange.getReadDeadlineNanos(); - if (seq1 != seq2 || d2 <= 0 || nowNanos <= d2) { + int d2 = exchange.getReadDeadlineTick(); + if (seq1 != seq2 || d2 <= 0 || nowTick < d2) { return; } @@ -504,8 +533,7 @@ private static void checkExchangeTimeout(H2Exchange exchange, long nowNanos) { private void workerLoop() { var batch = new ArrayList(64); IOException failure = null; - long lastTimeoutCheck = System.nanoTime(); - var readTimeoutFrequency = READ_TIMOUT_FREQUENCY; + long lastTimeoutCheck = System.currentTimeMillis(); try { while (running) { @@ -551,10 +579,11 @@ private void workerLoop() { } } - // Check for read timeouts periodically - long now = System.nanoTime(); - if (now - lastTimeoutCheck > readTimeoutFrequency) { - checkReadTimeouts(now); + // Check for read timeouts periodically using tick-based system + long now = System.currentTimeMillis(); + if (now - lastTimeoutCheck >= TIMEOUT_POLL_INTERVAL_MS) { + int tick = ++timeoutTick; + checkReadTimeouts(tick); lastTimeoutCheck = now; } } From 6217a2ef9cb59f6f2ca8de3b750bd90446b80405 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Tue, 9 Dec 2025 20:23:41 -0600 Subject: [PATCH 28/60] Optimize reads Short spin before park. Only way when waiting. --- .../java/http/client/h2/H2Exchange.java | 53 ++++++++++++++++--- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index 49d834ff8..68d0659da 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -88,6 +88,10 @@ enum ReadState { // Shared empty array to avoid allocation private static final byte[] EMPTY_DATA = new byte[0]; + // Short spin before full park - avoids heavy park/unpark for near-miss data arrivals + private static final int SPIN_WAITS = 2; + private static final long SPIN_WAIT_NANOS = 40_000L; // 40µs each + private final H2Muxer muxer; private final HttpRequest request; private volatile int streamId; @@ -123,6 +127,7 @@ enum ReadState { private final AtomicLong readSeq = new AtomicLong(); // Activity counter, incremented on read activity private volatile int readDeadlineTick; // 0 = no deadline, >0 = deadline tick private final AtomicBoolean readTimedOut = new AtomicBoolean(); // At-most-once timeout flag + private boolean waitingForData; // guarded by dataLock - true when VT is blocked waiting for data // Response state private volatile int statusCode = -1; @@ -305,12 +310,11 @@ void onHeadersEncoded(boolean endStream) { void deliverHeaders(List fields, boolean endStream) { dataLock.lock(); try { - // Process headers and update state (will be called from reader thread) - // The actual header processing happens here rather than in user thread - // to avoid needing a separate queue for headers pendingHeaders = fields; pendingHeadersEndStream = endStream; - dataAvailable.signalAll(); + if (waitingForData) { + dataAvailable.signalAll(); + } } finally { dataLock.unlock(); } @@ -383,6 +387,8 @@ int getWritePos() { void commitWrite(int bytesWritten, boolean endStream) { dataLock.lock(); try { + boolean wasEmpty = (writePos == readPos); + writePos += bytesWritten; if (bytesWritten > 0) { onReadActivity(); // Extend timeout when data arrives @@ -391,9 +397,12 @@ void commitWrite(int bytesWritten, boolean endStream) { this.endStreamReceived = true; this.readState = ReadState.DONE; clearReadDeadline(); // No more data expected, clear timeout - // Don't update streamState here. It will be updated when the user finishes reading, and we return -1. } - dataAvailable.signalAll(); + + // Only wake the reader if it's actually blocked waiting + if ((wasEmpty || endStream) && waitingForData) { + dataAvailable.signalAll(); + } } finally { dataLock.unlock(); } @@ -613,7 +622,12 @@ private void awaitEvent() throws IOException { try { // Wait for headers, error, or data (which also signals) while (pendingHeaders == null && readState != ReadState.ERROR && readState != ReadState.DONE) { - dataAvailable.await(); // Untimed: muxer watchdog handles timeout + waitingForData = true; + try { + dataAvailable.await(); // Untimed: muxer watchdog handles timeout + } finally { + waitingForData = false; + } } // Check for error @@ -1013,11 +1027,34 @@ int readFromBuffer(byte[] buf, int off, int len) throws IOException { } } + // Short timed waits to catch near-miss data arrivals + int spins = 0; + while (readPos == writePos && readState == ReadState.READING_DATA && spins++ < SPIN_WAITS) { + waitingForData = true; + try { + dataAvailable.awaitNanos(SPIN_WAIT_NANOS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Interrupted waiting for data", e); + } finally { + waitingForData = false; + } + } + + // If data arrived during spin, we're done waiting + if (readPos != writePos || readState != ReadState.READING_DATA) { + break; + } + + // Still nothing after short waits: do a full park + waitingForData = true; try { - dataAvailable.await(); // Untimed: muxer watchdog handles timeout + dataAvailable.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException("Interrupted waiting for data", e); + } finally { + waitingForData = false; } } From 72dd41bc04def06c796d06cf964e04e84a0327ba Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Tue, 9 Dec 2025 22:34:19 -0600 Subject: [PATCH 29/60] Remove spin, it hurt perf, updated window --- .../java/http/client/BenchmarkSupport.java | 10 ++++++ .../java/http/client/H2cScalingBenchmark.java | 11 +++++++ .../java/http/client/h2/H2Connection.java | 4 ++- .../java/http/client/h2/H2Constants.java | 4 +++ .../java/http/client/h2/H2Exchange.java | 32 +++---------------- 5 files changed, 33 insertions(+), 28 deletions(-) diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/BenchmarkSupport.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/BenchmarkSupport.java index 7d29d44f3..4668b3290 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/BenchmarkSupport.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/BenchmarkSupport.java @@ -155,12 +155,22 @@ public interface BenchmarkTask { /** * Simple counter for benchmark results. Used with @AuxCounters. + * JMH picks up public fields OR getter methods for aux counters. */ public static class RequestCounter { public long requests; public long errors; public Throwable firstError; + // Getter methods for JMH aux counters (some versions need these) + public long requests() { + return requests; + } + + public long errors() { + return errors; + } + public void reset() { requests = 0; errors = 0; diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java index ac7f78872..59765d0a2 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java @@ -205,6 +205,17 @@ public static class Counter extends BenchmarkSupport.RequestCounter { public void reset() { super.reset(); } + + // Override getters so JMH annotation processor sees them directly + @Override + public long requests() { + return requests; + } + + @Override + public long errors() { + return errors; + } } @Benchmark diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java index 9818f969d..61fc9d522 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java @@ -732,7 +732,9 @@ List decodeHeaders(byte[] headerBlock) throws IOException { // Called only from reader thread - no synchronization needed void consumeConnectionRecvWindow(int bytes) throws IOException { connectionRecvWindow -= bytes; - if (connectionRecvWindow < initialWindowSize / 2) { + // Send WINDOW_UPDATE when window drops below threshold to reduce control frame overhead + // while still leaving enough buffer to avoid server stalls + if (connectionRecvWindow < initialWindowSize / H2Constants.WINDOW_UPDATE_THRESHOLD_DIVISOR) { int increment = initialWindowSize - connectionRecvWindow; connectionRecvWindow += increment; muxer.queueControlFrame(0, diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java index e35199930..d817c3316 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java @@ -67,6 +67,10 @@ private H2Constants() {} // Window size limits static final int MAX_WINDOW_SIZE = Integer.MAX_VALUE; // 2^31 - 1 + // WINDOW_UPDATE threshold: send update when window drops below this fraction of initial size. + // Using 1/4 (25%) reduces control frame overhead while leaving enough buffer to avoid stalls. + static final int WINDOW_UPDATE_THRESHOLD_DIVISOR = 4; + // Error codes (RFC 9113 Section 7) static final int ERROR_NO_ERROR = 0x0; static final int ERROR_PROTOCOL_ERROR = 0x1; diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index 68d0659da..dedd16503 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -88,10 +88,6 @@ enum ReadState { // Shared empty array to avoid allocation private static final byte[] EMPTY_DATA = new byte[0]; - // Short spin before full park - avoids heavy park/unpark for near-miss data arrivals - private static final int SPIN_WAITS = 2; - private static final long SPIN_WAIT_NANOS = 40_000L; // 40µs each - private final H2Muxer muxer; private final HttpRequest request; private volatile int streamId; @@ -1011,7 +1007,7 @@ int readFromBuffer(byte[] buf, int off, int len) throws IOException { readResponseHeaders(); } - int bytesRead = 0; + int bytesRead; dataLock.lock(); try { // Wait for data, EOF, or error @@ -1027,26 +1023,7 @@ int readFromBuffer(byte[] buf, int off, int len) throws IOException { } } - // Short timed waits to catch near-miss data arrivals - int spins = 0; - while (readPos == writePos && readState == ReadState.READING_DATA && spins++ < SPIN_WAITS) { - waitingForData = true; - try { - dataAvailable.awaitNanos(SPIN_WAIT_NANOS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IOException("Interrupted waiting for data", e); - } finally { - waitingForData = false; - } - } - - // If data arrived during spin, we're done waiting - if (readPos != writePos || readState != ReadState.READING_DATA) { - break; - } - - // Still nothing after short waits: do a full park + // Wait for data to arrive waitingForData = true; try { dataAvailable.await(); @@ -1110,13 +1087,14 @@ int availableInBuffer() { /** * Update stream receive window after consuming data. * - *

      Sends WINDOW_UPDATE when the window gets below half of the initial size. + *

      Sends WINDOW_UPDATE when the window drops below the threshold defined by + * {@link H2Constants#WINDOW_UPDATE_THRESHOLD_DIVISOR}. * * @throws IOException if the write queue is full */ private void updateStreamRecvWindow(int bytesConsumed) throws IOException { streamRecvWindow -= bytesConsumed; - if (streamRecvWindow < initialWindowSize / 2) { + if (streamRecvWindow < initialWindowSize / H2Constants.WINDOW_UPDATE_THRESHOLD_DIVISOR) { int increment = initialWindowSize - streamRecvWindow; // Queue stream-level WINDOW_UPDATE - writer thread will send it muxer.queueControlFrame(streamId, H2Muxer.ControlFrameType.WINDOW_UPDATE, increment, writeTimeoutMs); From 0b205907a3c76f6581712f44a3f65ebe95938ec6 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Wed, 10 Dec 2025 10:53:17 -0600 Subject: [PATCH 30/60] Increase h2 connection buffer size to 64KB --- .../amazon/smithy/java/http/client/h2/H2Connection.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java index 61fc9d522..6a7f3d776 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java @@ -86,6 +86,7 @@ private enum State { } private static final InternalLogger LOGGER = InternalLogger.getLogger(H2Connection.class); + private static final int BUFFER_SIZE = 65536; private static final byte[] EMPTY_PAYLOAD = new byte[0]; private static final int SETTINGS_TIMEOUT_MS = 10_000; private static final int GRACEFUL_SHUTDOWN_MS = 1000; @@ -132,8 +133,8 @@ private enum State { public H2Connection(Socket socket, Route route, Duration readTimeout, Duration writeTimeout, int initialWindowSize) throws IOException { this.socket = socket; - var socketIn = new UnsyncBufferedInputStream(socket.getInputStream(), 8192); - this.socketOut = new UnsyncBufferedOutputStream(socket.getOutputStream(), 8192); + var socketIn = new UnsyncBufferedInputStream(socket.getInputStream(), BUFFER_SIZE); + this.socketOut = new UnsyncBufferedOutputStream(socket.getOutputStream(), BUFFER_SIZE); this.route = route; this.readTimeoutMs = readTimeout.toMillis(); this.writeTimeoutMs = writeTimeout.toMillis(); From 678cb7477ed08666308e598358b4c8accc724217 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Wed, 10 Dec 2025 10:53:57 -0600 Subject: [PATCH 31/60] Set window update to 33% to fill a bit more aggresively --- .../amazon/smithy/java/http/client/h2/H2Constants.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java index d817c3316..34c53a16d 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java @@ -68,8 +68,8 @@ private H2Constants() {} static final int MAX_WINDOW_SIZE = Integer.MAX_VALUE; // 2^31 - 1 // WINDOW_UPDATE threshold: send update when window drops below this fraction of initial size. - // Using 1/4 (25%) reduces control frame overhead while leaving enough buffer to avoid stalls. - static final int WINDOW_UPDATE_THRESHOLD_DIVISOR = 4; + // Using 1/3 (33%) reduces control frame overhead while leaving enough buffer to avoid stalls. + static final int WINDOW_UPDATE_THRESHOLD_DIVISOR = 3; // Error codes (RFC 9113 Section 7) static final int ERROR_NO_ERROR = 0x0; From d728343334778f3ccbb1f83ce8de4d9675355797 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Wed, 10 Dec 2025 10:54:12 -0600 Subject: [PATCH 32/60] Grow buffer by 4x to reduce resizing --- .../amazon/smithy/java/http/client/h2/H2Exchange.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index dedd16503..5f995b113 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -446,8 +446,8 @@ boolean ensureBufferSpace(int requiredSpace) { } } - // Need to grow buffer - get a larger one from pool - int newSize = dataBuffer.length * 2; + // Need to grow buffer - get a larger one from pool. Grow by 4x to reduce resizing frequency. + int newSize = dataBuffer.length * 4; while (newSize < writePos + requiredSpace) { newSize *= 2; } From 0cfaa09b218e42432da769ffd4aa8353d288cc08 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Wed, 10 Dec 2025 11:44:47 -0600 Subject: [PATCH 33/60] Fix netty benchmark to actually wait --- .../java/http/client/H2cScalingBenchmark.java | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java index 59765d0a2..9d4550604 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java @@ -306,7 +306,7 @@ public void nettyGetMb(Counter counter) throws Exception { var errors = new AtomicLong(); var firstError = new AtomicReference(); var running = new AtomicBoolean(true); - var activeTasks = new AtomicLong(concurrency); + var inFlight = new AtomicLong(0); // Track actually in-flight requests DefaultHttp2Headers headers = new DefaultHttp2Headers(); headers.method("GET"); @@ -318,19 +318,20 @@ public void nettyGetMb(Counter counter) throws Exception { Runnable[] makeRequest = new Runnable[1]; makeRequest[0] = () -> { if (!running.get()) { - activeTasks.decrementAndGet(); - return; + return; // Don't start new requests when stopped } + inFlight.incrementAndGet(); // Track in-flight + int idx = connectionIndex.getAndIncrement() % nettyStreamBootstraps.size(); nettyStreamBootstraps.get(idx).open().addListener(future -> { if (!future.isSuccess()) { + inFlight.decrementAndGet(); errors.incrementAndGet(); firstError.compareAndSet(null, future.cause()); - if (!running.get()) - activeTasks.decrementAndGet(); - else - makeRequest[0].run(); + if (running.get()) { + makeRequest[0].run(); // Retry only if still running + } return; } @@ -351,18 +352,24 @@ protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame frame) { boolean endStream = (frame instanceof Http2HeadersFrame hf && hf.isEndStream()) || (frame instanceof Http2DataFrame df2 && df2.isEndStream()); if (endStream) { + inFlight.decrementAndGet(); requests.incrementAndGet(); ctx.close(); - makeRequest[0].run(); + if (running.get()) { + makeRequest[0].run(); // Start next only if still running + } } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + inFlight.decrementAndGet(); errors.incrementAndGet(); firstError.compareAndSet(null, cause); ctx.close(); - makeRequest[0].run(); + if (running.get()) { + makeRequest[0].run(); // Retry only if still running + } } }); @@ -370,19 +377,25 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { }); }; + // Start initial concurrent requests for (int i = 0; i < concurrency; i++) { makeRequest[0].run(); } + // Run for 1 second Thread.sleep(1000); running.set(false); - // Don't wait long - just let in-flight requests drain briefly - long deadline = System.currentTimeMillis() + 100; - while (activeTasks.get() > 0 && System.currentTimeMillis() < deadline) { + // Wait for ALL in-flight requests to complete (reasonable timeout) + long deadline = System.currentTimeMillis() + 10000; // 10 second max + while (inFlight.get() > 0 && System.currentTimeMillis() < deadline) { Thread.sleep(10); } + if (inFlight.get() > 0) { + System.out.println("WARNING: " + inFlight.get() + " requests still in-flight after timeout"); + } + counter.requests = requests.get(); counter.errors = errors.get(); counter.firstError = firstError.get(); From e92b195cb2c646c385cc8255c44533fa4377949e Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Wed, 10 Dec 2025 11:45:43 -0600 Subject: [PATCH 34/60] Optimize h2 input stream buffering --- .../http/client/h2/H2DataInputStream.java | 33 +++++++++++++++++-- .../java/http/client/h2/H2Exchange.java | 10 ++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java index 175ebf909..c3fa2a869 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; /** * Input stream for reading response body from DATA frames. @@ -16,8 +17,11 @@ * per-frame allocations and reduces copying. */ final class H2DataInputStream extends InputStream { + + private static final int TRANSFER_BUFFER_SIZE = 16384; private final H2Exchange exchange; private boolean closed = false; + private byte[] singleBuff; H2DataInputStream(H2Exchange exchange) { this.exchange = exchange; @@ -28,9 +32,11 @@ public int read() throws IOException { if (closed) { return -1; } - byte[] buf = new byte[1]; - int n = exchange.readFromBuffer(buf, 0, 1); - return n == 1 ? (buf[0] & 0xFF) : -1; + if (singleBuff == null) { + singleBuff = new byte[1]; + } + int n = exchange.readFromBuffer(singleBuff, 0, 1); + return n == 1 ? (singleBuff[0] & 0xFF) : -1; } @Override @@ -56,4 +62,25 @@ public int available() { public void close() { closed = true; } + + @Override + public long transferTo(OutputStream out) throws IOException { + if (closed) { + return 0; + } + + // Borrow buffer from pool instead of allocating + byte[] buffer = exchange.borrowBuffer(TRANSFER_BUFFER_SIZE); + try { + long transferred = 0; + int read; + while ((read = exchange.readFromBuffer(buffer, 0, buffer.length)) >= 0) { + out.write(buffer, 0, read); + transferred += read; + } + return transferred; + } finally { + exchange.returnBuffer(buffer); + } + } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index 5f995b113..d9d4b2b09 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -266,6 +266,16 @@ H2Muxer getMuxer() { return muxer; } + /** + * Borrow a buffer from the muxer's pool. + * + * @param minSize minimum size needed + * @return a buffer of at least minSize bytes + */ + byte[] borrowBuffer(int minSize) { + return muxer.borrowBuffer(minSize); + } + /** * Return a buffer to the muxer's pool. * From 65ac2d913282bf74980c5174d18cfa5408c7315e Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Wed, 10 Dec 2025 15:47:31 -0600 Subject: [PATCH 35/60] Make sweeping optimizations --- .../java/http/client/BenchmarkSupport.java | 24 +- .../java/http/client/H2cScalingBenchmark.java | 277 ++++-- .../java/http/client/BenchmarkServer.java | 145 ++- .../{BufferPool.java => ByteAllocator.java} | 23 +- .../client/DelegatedClosingInputStream.java | 5 + .../client/DelegatedClosingOutputStream.java | 11 + .../client/UnsyncBufferedInputStream.java | 139 ++- .../connection/H2ConnectionManager.java | 169 ++-- .../connection/HttpConnectionFactory.java | 53 +- .../client/connection/HttpConnectionPool.java | 3 +- .../connection/HttpConnectionPoolBuilder.java | 32 + .../smithy/java/http/client/h2/DataChunk.java | 11 + .../http/client/h2/FlowControlWindow.java | 85 +- .../java/http/client/h2/H2Connection.java | 425 +++++---- .../java/http/client/h2/H2Constants.java | 7 +- .../http/client/h2/H2DataInputStream.java | 159 +++- .../http/client/h2/H2DataOutputStream.java | 44 +- .../java/http/client/h2/H2Exchange.java | 866 ++++++++--------- .../java/http/client/h2/H2FrameCodec.java | 892 ++++++++++-------- .../smithy/java/http/client/h2/H2Muxer.java | 506 ++++------ .../java/http/client/h2/H2MuxerWorkItem.java | 113 +++ .../client/h2/H2RequestHeaderEncoder.java | 221 +++++ .../client/h2/H2ResponseHeaderProcessor.java | 180 ++++ .../java/http/client/h2/H2StreamState.java | 357 +++++++ .../java/http/client/h2/PendingWrite.java | 26 +- .../java/http/client/h2/StreamRegistry.java | 13 +- .../http/client/h2/hpack/HpackEncoder.java | 68 +- .../java/http/client/h2/hpack/Huffman.java | 43 +- .../client/UnsyncBufferedInputStreamTest.java | 239 +++++ ...erPoolTest.java => ByteAllocatorTest.java} | 44 +- .../java/http/client/h2/H2FrameCodecTest.java | 341 ++++--- .../http/client/h2/H2FrameTestSuiteTest.java | 109 ++- .../java/io/ByteBufferOutputStream.java | 29 + .../io/datastream/ByteBufferDataStream.java | 4 +- 34 files changed, 3653 insertions(+), 2010 deletions(-) rename http/http-client/src/main/java/software/amazon/smithy/java/http/client/{BufferPool.java => ByteAllocator.java} (86%) create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/DataChunk.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2MuxerWorkItem.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2RequestHeaderEncoder.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessor.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamState.java rename http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/{BufferPoolTest.java => ByteAllocatorTest.java} (82%) diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/BenchmarkSupport.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/BenchmarkSupport.java index 4668b3290..18df8eed7 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/BenchmarkSupport.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/BenchmarkSupport.java @@ -15,8 +15,7 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLContext; @@ -99,34 +98,32 @@ public static String getServerStats(HttpClient client, String baseUrl) throws Ex } /** - * Run a benchmark loop with virtual threads. + * Run a benchmark loop with virtual threads until totalRequests is reached. * - * @param concurrency number of virtual threads - * @param durationMs how long to run + * @param concurrency number of virtual threads generating load + * @param totalRequests total requests to complete before stopping * @param task the task each thread runs in a loop * @param context context passed to task (avoids lambda allocation) * @param counter output counter for requests/errors */ public static void runBenchmark( int concurrency, - long durationMs, + int totalRequests, BenchmarkTask task, T context, RequestCounter counter ) throws InterruptedException { - var requests = new AtomicLong(); + var completed = new AtomicInteger(0); var errors = new AtomicLong(); var firstError = new AtomicReference(); - var running = new AtomicBoolean(true); var latch = new CountDownLatch(concurrency); try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { for (int i = 0; i < concurrency; i++) { executor.submit(() -> { try { - while (running.get()) { + while (completed.getAndIncrement() < totalRequests) { task.run(context); - requests.incrementAndGet(); } } catch (Exception e) { errors.incrementAndGet(); @@ -137,13 +134,10 @@ public static void runBenchmark( }); } - Thread.sleep(durationMs); - running.set(false); - // Don't wait long - VTs may be blocked on in-flight requests - latch.await(100, TimeUnit.MILLISECONDS); + latch.await(); // Wait for all work to complete } - counter.requests = requests.get(); + counter.requests = completed.get(); counter.errors = errors.get(); counter.firstError = firstError.get(); } diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java index 9d4550604..f6ffbf436 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java @@ -17,7 +17,10 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; import io.netty.handler.codec.http2.Http2DataFrame; import io.netty.handler.codec.http2.Http2FrameCodecBuilder; import io.netty.handler.codec.http2.Http2HeadersFrame; @@ -25,17 +28,6 @@ import io.netty.handler.codec.http2.Http2StreamChannel; import io.netty.handler.codec.http2.Http2StreamChannelBootstrap; import io.netty.handler.codec.http2.Http2StreamFrame; -import java.io.OutputStream; -import java.net.InetSocketAddress; -import java.net.URI; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; import org.openjdk.jmh.annotations.AuxCounters; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -56,6 +48,17 @@ import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; import software.amazon.smithy.java.io.datastream.DataStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + /** * HTTP/2 cleartext (h2c) client scaling benchmark. * @@ -79,11 +82,11 @@ @State(Scope.Benchmark) public class H2cScalingBenchmark { - @Param({"1000"}) + @Param({"5000"}) private int concurrency; - @Param({"1" - //, "5" + @Param({"3", + "5" //, "10", "20", "50" }) private int connections; @@ -301,13 +304,65 @@ public void smithyGetMb(Counter counter) throws InterruptedException { @Benchmark @Threads(1) - public void nettyGetMb(Counter counter) throws Exception { - var requests = new AtomicLong(); - var errors = new AtomicLong(); - var firstError = new AtomicReference(); - var running = new AtomicBoolean(true); - var inFlight = new AtomicLong(0); // Track actually in-flight requests + public void netty(Counter counter) throws Exception { + DefaultHttp2Headers headers = new DefaultHttp2Headers(); + headers.method("GET"); + headers.path("/get"); + headers.scheme("http"); + headers.authority("localhost:18081"); + + var connectionIndex = new AtomicInteger(0); + + BenchmarkSupport.runBenchmark(concurrency, 1000, (DefaultHttp2Headers h) -> { + var latch = new CountDownLatch(1); + var error = new AtomicReference(); + + int idx = connectionIndex.getAndIncrement() % nettyStreamBootstraps.size(); + nettyStreamBootstraps.get(idx).open().addListener(future -> { + if (!future.isSuccess()) { + error.set(future.cause()); + latch.countDown(); + return; + } + + Http2StreamChannel streamChannel = (Http2StreamChannel) future.get(); + streamChannel.pipeline().addLast(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame frame) { + if (frame instanceof Http2DataFrame df) { + df.content().skipBytes(df.content().readableBytes()); + } + boolean endStream = (frame instanceof Http2HeadersFrame hf && hf.isEndStream()) + || (frame instanceof Http2DataFrame df2 && df2.isEndStream()); + if (endStream) { + ctx.close(); + latch.countDown(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + error.set(cause); + ctx.close(); + latch.countDown(); + } + }); + + streamChannel.writeAndFlush(new io.netty.handler.codec.http2.DefaultHttp2HeadersFrame(h, true)); + }); + + latch.await(); + if (error.get() != null) { + throw new RuntimeException(error.get()); + } + }, headers, counter); + counter.logErrors("Netty H2c GET"); + } + + @Benchmark + @Threads(1) + public void nettyGetMb(Counter counter) throws Exception { DefaultHttp2Headers headers = new DefaultHttp2Headers(); headers.method("GET"); headers.path("/getmb"); @@ -315,23 +370,16 @@ public void nettyGetMb(Counter counter) throws Exception { headers.authority("localhost:18081"); var connectionIndex = new AtomicInteger(0); - Runnable[] makeRequest = new Runnable[1]; - makeRequest[0] = () -> { - if (!running.get()) { - return; // Don't start new requests when stopped - } - inFlight.incrementAndGet(); // Track in-flight + BenchmarkSupport.runBenchmark(concurrency, 1000, (DefaultHttp2Headers h) -> { + var latch = new CountDownLatch(1); + var error = new AtomicReference(); int idx = connectionIndex.getAndIncrement() % nettyStreamBootstraps.size(); nettyStreamBootstraps.get(idx).open().addListener(future -> { if (!future.isSuccess()) { - inFlight.decrementAndGet(); - errors.incrementAndGet(); - firstError.compareAndSet(null, future.cause()); - if (running.get()) { - makeRequest[0].run(); // Retry only if still running - } + error.set(future.cause()); + latch.countDown(); return; } @@ -352,54 +400,155 @@ protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame frame) { boolean endStream = (frame instanceof Http2HeadersFrame hf && hf.isEndStream()) || (frame instanceof Http2DataFrame df2 && df2.isEndStream()); if (endStream) { - inFlight.decrementAndGet(); - requests.incrementAndGet(); ctx.close(); - if (running.get()) { - makeRequest[0].run(); // Start next only if still running - } + latch.countDown(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - inFlight.decrementAndGet(); - errors.incrementAndGet(); - firstError.compareAndSet(null, cause); + error.set(cause); ctx.close(); - if (running.get()) { - makeRequest[0].run(); // Retry only if still running + latch.countDown(); + } + }); + + streamChannel.writeAndFlush(new io.netty.handler.codec.http2.DefaultHttp2HeadersFrame(h, true)); + }); + + latch.await(); + if (error.get() != null) { + throw new RuntimeException(error.get()); + } + }, headers, counter); + + System.out.println("Netty H2c GET 1MB: " + counter.requests + " requests, " + counter.errors + " errors"); + counter.logErrors("Netty H2c GET 1MB"); + } + + @Benchmark + @Threads(1) + public void nettyPost(Counter counter) throws Exception { + DefaultHttp2Headers headers = new DefaultHttp2Headers(); + headers.method("POST"); + headers.path("/post"); + headers.scheme("http"); + headers.authority("localhost:18081"); + headers.setInt("content-length", BenchmarkSupport.POST_PAYLOAD.length); + + var connectionIndex = new AtomicInteger(0); + + BenchmarkSupport.runBenchmark(concurrency, 1000, (DefaultHttp2Headers h) -> { + var latch = new CountDownLatch(1); + var error = new AtomicReference(); + + int idx = connectionIndex.getAndIncrement() % nettyStreamBootstraps.size(); + nettyStreamBootstraps.get(idx).open().addListener(future -> { + if (!future.isSuccess()) { + error.set(future.cause()); + latch.countDown(); + return; + } + + Http2StreamChannel streamChannel = (Http2StreamChannel) future.get(); + streamChannel.pipeline().addLast(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame frame) { + if (frame instanceof Http2DataFrame df) { + df.content().skipBytes(df.content().readableBytes()); } + boolean endStream = (frame instanceof Http2HeadersFrame hf && hf.isEndStream()) + || (frame instanceof Http2DataFrame df2 && df2.isEndStream()); + if (endStream) { + ctx.close(); + latch.countDown(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + error.set(cause); + ctx.close(); + latch.countDown(); } }); - streamChannel.writeAndFlush(new io.netty.handler.codec.http2.DefaultHttp2HeadersFrame(headers, true)); + // Send headers (endStream=false since we have a body) + streamChannel.write(new DefaultHttp2HeadersFrame(h, false)); + // Send body with endStream=true + streamChannel.writeAndFlush(new DefaultHttp2DataFrame( + Unpooled.wrappedBuffer(BenchmarkSupport.POST_PAYLOAD), true)); }); - }; - // Start initial concurrent requests - for (int i = 0; i < concurrency; i++) { - makeRequest[0].run(); - } + latch.await(); + if (error.get() != null) { + throw new RuntimeException(error.get()); + } + }, headers, counter); - // Run for 1 second - Thread.sleep(1000); - running.set(false); + counter.logErrors("Netty H2c POST"); + } - // Wait for ALL in-flight requests to complete (reasonable timeout) - long deadline = System.currentTimeMillis() + 10000; // 10 second max - while (inFlight.get() > 0 && System.currentTimeMillis() < deadline) { - Thread.sleep(10); - } + @Benchmark + @Threads(1) + public void nettyPutMb(Counter counter) throws Exception { + DefaultHttp2Headers headers = new DefaultHttp2Headers(); + headers.method("PUT"); + headers.path("/putmb"); + headers.scheme("http"); + headers.authority("localhost:18081"); + headers.setInt("content-length", BenchmarkSupport.MB_PAYLOAD.length); - if (inFlight.get() > 0) { - System.out.println("WARNING: " + inFlight.get() + " requests still in-flight after timeout"); - } + var connectionIndex = new AtomicInteger(0); - counter.requests = requests.get(); - counter.errors = errors.get(); - counter.firstError = firstError.get(); - System.out.println("Netty H2c GET 1MB: " + counter.requests + " requests, " + counter.errors + " errors"); - counter.logErrors("Netty H2c GET 1MB"); + BenchmarkSupport.runBenchmark(concurrency, 1000, (DefaultHttp2Headers h) -> { + var latch = new CountDownLatch(1); + var error = new AtomicReference(); + + int idx = connectionIndex.getAndIncrement() % nettyStreamBootstraps.size(); + nettyStreamBootstraps.get(idx).open().addListener(future -> { + if (!future.isSuccess()) { + error.set(future.cause()); + latch.countDown(); + return; + } + + Http2StreamChannel streamChannel = (Http2StreamChannel) future.get(); + streamChannel.pipeline().addLast(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame frame) { + if (frame instanceof Http2DataFrame df) { + df.content().skipBytes(df.content().readableBytes()); + } + boolean endStream = (frame instanceof Http2HeadersFrame hf && hf.isEndStream()) + || (frame instanceof Http2DataFrame df2 && df2.isEndStream()); + if (endStream) { + ctx.close(); + latch.countDown(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + error.set(cause); + ctx.close(); + latch.countDown(); + } + }); + + // Send headers (endStream=false since we have a body) + streamChannel.write(new DefaultHttp2HeadersFrame(h, false)); + // Send body with endStream=true + streamChannel.writeAndFlush(new DefaultHttp2DataFrame( + Unpooled.wrappedBuffer(BenchmarkSupport.MB_PAYLOAD), true)); + }); + + latch.await(); + if (error.get() != null) { + throw new RuntimeException(error.get()); + } + }, headers, counter); + + counter.logErrors("Netty H2c PUT 1MB"); } } diff --git a/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java index f1116eaa2..babf55813 100644 --- a/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java +++ b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java @@ -39,7 +39,9 @@ import io.netty.handler.codec.http2.Http2FrameStream; import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.Http2HeadersFrame; +import io.netty.handler.codec.http2.Http2MultiplexHandler; import io.netty.handler.codec.http2.Http2SecurityUtil; +import io.netty.handler.codec.http2.Http2StreamFrame; import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; @@ -81,6 +83,15 @@ public final class BenchmarkServer { public static final int DEFAULT_H2_PORT = 18443; public static final int DEFAULT_H2C_PORT = 18081; + // HTTP/2 settings - tunable for benchmarking + private static final int H2_MAX_CONCURRENT_STREAMS = 20000; + private static final int H2_INITIAL_WINDOW_SIZE = 1024 * 1024 * 2; + private static final int H2_MAX_FRAME_SIZE = 1024 * 64; + + // HTTP/2 TLS settings (slightly more conservative) + private static final int H2_TLS_MAX_CONCURRENT_STREAMS = 10000; + private static final int H2_TLS_INITIAL_WINDOW_SIZE = 1024 * 1024; + private final EventLoopGroup bossGroup; private final EventLoopGroup workerGroup; private final Channel h1ServerChannel; @@ -201,8 +212,9 @@ private Channel startH2cServer(int port) throws InterruptedException { @Override public void initChannel(SocketChannel ch) { var settings = io.netty.handler.codec.http2.Http2Settings.defaultSettings() - .maxConcurrentStreams(20000) - .initialWindowSize(2097152); + .maxConcurrentStreams(H2_MAX_CONCURRENT_STREAMS) + .initialWindowSize(H2_INITIAL_WINDOW_SIZE) + .maxFrameSize(H2_MAX_FRAME_SIZE); ch.pipeline() .addLast( Http2FrameCodecBuilder.forServer() @@ -210,7 +222,12 @@ public void initChannel(SocketChannel ch) { .autoAckSettingsFrame(true) .autoAckPingFrame(true) .build(), - Http2RequestHandler.INSTANCE); + new Http2MultiplexHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + ch.pipeline().addLast(new Http2StreamHandler()); + } + })); } }); @@ -286,14 +303,20 @@ private static class Http2OrHttpHandler extends ApplicationProtocolNegotiationHa protected void configurePipeline(ChannelHandlerContext ctx, String protocol) { if (ApplicationProtocolNames.HTTP_2.equals(protocol)) { var settings = io.netty.handler.codec.http2.Http2Settings.defaultSettings() - .maxConcurrentStreams(10000) - .initialWindowSize(1048576); // 1MB stream window + .maxConcurrentStreams(H2_TLS_MAX_CONCURRENT_STREAMS) + .initialWindowSize(H2_TLS_INITIAL_WINDOW_SIZE) + .maxFrameSize(H2_MAX_FRAME_SIZE); ctx.pipeline() .addLast( Http2FrameCodecBuilder.forServer() .initialSettings(settings) .build(), - Http2RequestHandler.INSTANCE); + new Http2MultiplexHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + ch.pipeline().addLast(new Http2StreamHandler()); + } + })); } else { ctx.pipeline() .addLast( @@ -305,11 +328,12 @@ protected void configurePipeline(ChannelHandlerContext ctx, String protocol) { } /** - * Handler for HTTP/2 requests using the frame codec API. + * Per-stream handler for HTTP/2 requests. + * + *

      Each stream gets its own instance via Http2MultiplexHandler. + * Flow control is handled automatically by Netty when using stream channels. */ - @io.netty.channel.ChannelHandler.Sharable - private static class Http2RequestHandler extends ChannelInboundHandlerAdapter { - static final Http2RequestHandler INSTANCE = new Http2RequestHandler(); + private static class Http2StreamHandler extends SimpleChannelInboundHandler { private static final Http2Headers RESPONSE_HEADERS = new DefaultHttp2Headers(true, 3) .status("200") .set("content-type", "application/json") @@ -322,109 +346,54 @@ private static class Http2RequestHandler extends ChannelInboundHandlerAdapter { .set("content-type", "application/octet-stream") .setInt("content-length", MB_CONTENT.length); - private static final java.util.concurrent.atomic.AtomicInteger connectionCount = - new java.util.concurrent.atomic.AtomicInteger(); - private static final java.util.concurrent.ConcurrentHashMap streamCounts = - new java.util.concurrent.ConcurrentHashMap<>(); - - @Override - public void handlerAdded(ChannelHandlerContext ctx) { - int count = connectionCount.incrementAndGet(); - String connId = ctx.channel().id().asShortText(); - streamCounts.put(connId, new java.util.concurrent.atomic.AtomicInteger()); - System.out.println("[H2] Connection opened: " + connId + " (total: " + count + ")"); - } - @Override - public void handlerRemoved(ChannelHandlerContext ctx) { - int count = connectionCount.decrementAndGet(); - String connId = ctx.channel().id().asShortText(); - java.util.concurrent.atomic.AtomicInteger streams = streamCounts.remove(connId); - System.out.println("[H2] Connection closed: " + connId + " (handled " - + (streams != null ? streams.get() : 0) + " streams, remaining: " + count + ")"); - } - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) { - if (msg instanceof Http2HeadersFrame headersFrame) { + protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame frame) { + if (frame instanceof Http2HeadersFrame headersFrame) { CharSequence path = headersFrame.headers().path(); if ("/reset".contentEquals(path)) { System.gc(); - System.out.println("[H2] Reset - Active connections: " + connectionCount.get()); - streamCounts - .forEach((id, count) -> System.out.println(" " + id + ": " + count.get() + " streams")); + System.out.println("[H2] Reset triggered"); Http2Headers resetHeaders = new DefaultHttp2Headers(true, 1).status("200"); - ctx.writeAndFlush(new DefaultHttp2HeadersFrame(resetHeaders, true).stream(headersFrame.stream())); + ctx.writeAndFlush(new DefaultHttp2HeadersFrame(resetHeaders, true)); } else if ("/stats".contentEquals(path)) { - StringBuilder json = new StringBuilder("{\"connections\":"); - json.append(connectionCount.get()).append(",\"streams\":{"); - streamCounts.forEach( - (id, count) -> json.append("\"").append(id).append("\":").append(count.get()).append(",")); - if (json.charAt(json.length() - 1) == ',') - json.setLength(json.length() - 1); + StringBuilder json = new StringBuilder("{"); + json.append("\"settings\":{"); + json.append("\"maxConcurrentStreams\":").append(H2_MAX_CONCURRENT_STREAMS).append(","); + json.append("\"initialWindowSize\":").append(H2_INITIAL_WINDOW_SIZE).append(","); + json.append("\"maxFrameSize\":").append(H2_MAX_FRAME_SIZE); json.append("}}"); byte[] body = json.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8); Http2Headers statsHeaders = new DefaultHttp2Headers(true, 2) .status("200") .setInt("content-length", body.length); - ctx.write(new DefaultHttp2HeadersFrame(statsHeaders, false).stream(headersFrame.stream())); - ctx.writeAndFlush(new DefaultHttp2DataFrame(Unpooled.wrappedBuffer(body), true) - .stream(headersFrame.stream())); + ctx.write(new DefaultHttp2HeadersFrame(statsHeaders, false)); + ctx.writeAndFlush(new DefaultHttp2DataFrame(Unpooled.wrappedBuffer(body), true)); } else if ("/post".contentEquals(path) || "/putmb".contentEquals(path)) { - // POST/PUT with body - wait for data frames, then send empty 200 + // POST/PUT with body - wait for data frames if (headersFrame.isEndStream()) { // No body, respond immediately - var counter = streamCounts.get(ctx.channel().id().asShortText()); - if (counter != null) - counter.incrementAndGet(); - sendEmptyResponse(ctx, headersFrame.stream()); + ctx.writeAndFlush(new DefaultHttp2HeadersFrame(EMPTY_RESPONSE_HEADERS, true)); } - // else: wait for DATA frames + // else: wait for DATA frames with endStream } else if ("/getmb".contentEquals(path)) { - // Return 1MB response if (headersFrame.isEndStream()) { - var counter = streamCounts.get(ctx.channel().id().asShortText()); - if (counter != null) - counter.incrementAndGet(); - sendMbResponse(ctx, headersFrame.stream()); + ctx.write(new DefaultHttp2HeadersFrame(MB_RESPONSE_HEADERS, false)); + ctx.writeAndFlush(new DefaultHttp2DataFrame(Unpooled.wrappedBuffer(MB_CONTENT), true)); } } else if (headersFrame.isEndStream()) { - var counter = streamCounts.get(ctx.channel().id().asShortText()); - if (counter != null) - counter.incrementAndGet(); - sendResponse(ctx, headersFrame.stream()); + // Simple GET - respond with JSON body + ctx.write(new DefaultHttp2HeadersFrame(RESPONSE_HEADERS, false)); + ctx.writeAndFlush(new DefaultHttp2DataFrame(Unpooled.wrappedBuffer(CONTENT), true)); } - } else if (msg instanceof Http2DataFrame dataFrame) { - dataFrame.release(); + } else if (frame instanceof Http2DataFrame dataFrame) { + // Data consumed - flow control handled automatically by Http2MultiplexHandler if (dataFrame.isEndStream()) { - var counter = streamCounts.get(ctx.channel().id().asShortText()); - if (counter != null) - counter.incrementAndGet(); - // POST requests with body get empty response - sendEmptyResponse(ctx, dataFrame.stream()); + // POST/PUT complete - send empty response + ctx.writeAndFlush(new DefaultHttp2HeadersFrame(EMPTY_RESPONSE_HEADERS, true)); } } } - private void sendResponse(ChannelHandlerContext ctx, Http2FrameStream stream) { - ctx.write(new DefaultHttp2HeadersFrame(RESPONSE_HEADERS, false).stream(stream)); - ctx.writeAndFlush(new DefaultHttp2DataFrame( - Unpooled.wrappedBuffer(CONTENT), - true).stream(stream)); - } - - private void sendEmptyResponse(ChannelHandlerContext ctx, Http2FrameStream stream) { - ctx.writeAndFlush(new DefaultHttp2HeadersFrame(EMPTY_RESPONSE_HEADERS, true).stream(stream)); - } - - private void sendMbResponse(ChannelHandlerContext ctx, Http2FrameStream stream) { - ctx.write(new DefaultHttp2HeadersFrame(MB_RESPONSE_HEADERS, false).stream(stream)); - ctx.writeAndFlush(new DefaultHttp2DataFrame( - Unpooled.wrappedBuffer(MB_CONTENT), - true).stream(stream)); - } - @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { ctx.close(); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/BufferPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ByteAllocator.java similarity index 86% rename from http/http-client/src/main/java/software/amazon/smithy/java/http/client/BufferPool.java rename to http/http-client/src/main/java/software/amazon/smithy/java/http/client/ByteAllocator.java index 58b1f1958..45e18982a 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/BufferPool.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ByteAllocator.java @@ -9,11 +9,10 @@ import java.util.concurrent.atomic.AtomicReferenceArray; /** - * A very small, very fast, lock-free buffer pool for reusing byte arrays. + * A lock-free byte array allocator with optional pooling to reduce GC pressure. * - *

      Designed for use by a single HTTP/2 connection to reduce GC pressure - * from repeated request/response cycles. Each connection should have its - * own pool to minimize contention. + *

      Designed for use by a single HTTP/2 connection. Each connection should have + * its own allocator to minimize contention. * *

      Implementation details: *

        @@ -23,13 +22,13 @@ * it, which is fine for a GC-reducing pool. *
      * - *

      The pool has a configurable maximum count and poolable size. Buffers larger + *

      The allocator has a configurable maximum count and poolable size. Buffers larger * than {@code maxPoolableSize} are never pooled. Requests larger than * {@code maxBufferSize} are rejected. * - *

      Thread-safe: multiple threads can borrow and return buffers concurrently. + *

      Thread-safe: multiple threads can borrow and release buffers concurrently. */ -public final class BufferPool { +public final class ByteAllocator { // LIFO stack of pooled buffers: [0, top) are valid entries. private final AtomicReferenceArray stack; @@ -41,14 +40,14 @@ public final class BufferPool { private final int defaultBufferSize; /** - * Create a buffer pool. + * Create a byte allocator with pooling. * * @param maxPoolCount maximum number of buffers to keep in pool * @param maxBufferSize hard limit on buffer size (throws if exceeded) * @param maxPoolableSize buffers larger than this are not pooled (but still allowed) * @param defaultBufferSize default size for new buffers when pool is empty */ - public BufferPool(int maxPoolCount, int maxBufferSize, int maxPoolableSize, int defaultBufferSize) { + public ByteAllocator(int maxPoolCount, int maxBufferSize, int maxPoolableSize, int defaultBufferSize) { if (maxPoolCount <= 0) { throw new IllegalArgumentException("maxPoolCount must be > 0"); } @@ -71,8 +70,12 @@ public BufferPool(int maxPoolCount, int maxBufferSize, int maxPoolableSize, int *

      If a pooled buffer is available and large enough, it's returned. * Otherwise, a new buffer is allocated with at least {@code minSize} bytes. * + *

      Important: The returned buffer may be larger than {@code minSize}. + * Callers must track the actual data length separately and not rely on + * {@code buffer.length} to determine data boundaries. + * * @param minSize minimum buffer size needed - * @return a buffer of at least minSize bytes + * @return a buffer of at least minSize bytes (may be larger) * @throws IllegalArgumentException if minSize exceeds maxBufferSize */ public byte[] borrow(int minSize) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java index 84f0fe284..95d25e2f8 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java @@ -24,6 +24,11 @@ public DelegatedClosingInputStream(InputStream delegate, CloseCallback closeCall this.closeCallback = closeCallback; } + @Override + public int read(byte[] b, int off, int len) throws IOException { + return in.read(b, off, len); + } + @Override public void close() throws IOException { if (closed.compareAndSet(false, true)) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java index 3728d2d73..58d77e062 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java @@ -24,6 +24,17 @@ public DelegatedClosingOutputStream(OutputStream delegate, CloseCallback closeCa this.closeCallback = closeCallback; } + @Override + public void write(byte[] b, int off, int len) throws IOException { + // Override to delegate directly - FilterOutputStream's default loops byte-by-byte + out.write(b, off, len); + } + + @Override + public void flush() throws IOException { + out.flush(); + } + @Override public void close() throws IOException { if (closed.compareAndSet(false, true)) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStream.java index 30e23dc72..87e8e7e52 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStream.java @@ -11,6 +11,8 @@ /** * A buffered input stream like {@link java.io.BufferedInputStream}, but without synchronization. + * + *

      This class exposes its guts for optimal performance. Be responsible, and note the warnings on each method. */ public final class UnsyncBufferedInputStream extends InputStream { private final InputStream in; @@ -83,7 +85,7 @@ public int read(byte[] b, int off, int len) throws IOException { } } - // If caller wants something large, bypass our buffer + // If remaining request is large, bypass our buffer to avoid double-copy if (len >= buf.length) { int direct = in.read(b, off, len); if (direct < 0) { @@ -92,7 +94,7 @@ public int read(byte[] b, int off, int len) throws IOException { return n + direct; } - // Otherwise, refill and copy from buffer + // For smaller remaining requests, refill buffer and copy if (fill() <= 0) { return n == 0 ? -1 : n; } @@ -177,6 +179,139 @@ public long transferTo(OutputStream out) throws IOException { return transferred; } + /** + * Read directly from the underlying stream, bypassing this buffer entirely. + * + *

      This is useful when the caller knows the buffer is empty (e.g., after + * draining via {@link #consume}) and wants to avoid the buffer fill/check overhead. + * + * @param b destination buffer + * @param off offset in destination + * @param len maximum bytes to read + * @return bytes read, or -1 on EOF + * @throws IOException if an I/O error occurs + * @throws IllegalStateException if the buffer is not empty + */ + public int readDirect(byte[] b, int off, int len) throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + if (pos < limit) { + throw new IllegalStateException("Buffer not empty: " + (limit - pos) + " bytes remaining"); + } + return in.read(b, off, len); + } + + /** + * Returns the internal buffer array. + * + *

      WARNING: The caller must not modify the buffer contents. + * This method is provided for zero-copy read access only. + * + * @return the internal buffer array + */ + public byte[] buffer() { + return buf; + } + + /** + * Returns the current read position in the internal buffer. + * + *

      Valid data in the buffer spans from {@code position()} to {@code limit()}. + * + * @return the current read position + */ + public int position() { + return pos; + } + + /** + * Returns the current limit of valid data in the internal buffer. + * + *

      Valid data in the buffer spans from {@code position()} to {@code limit()}. + * + * @return the limit of valid data + */ + public int limit() { + return limit; + } + + /** + * Returns the number of bytes currently buffered and available for reading. + * + *

      This is equivalent to {@code limit() - position()}. + * + * @return the number of buffered bytes available + */ + public int buffered() { + return limit - pos; + } + + /** + * Advances the internal read position, consuming bytes from the buffer. + * + *

      This is used after directly reading from the buffer via {@link #buffer()}. + * + * @param n number of bytes to consume + * @throws IndexOutOfBoundsException if n > buffered() + */ + public void consume(int n) { + if (n < 0 || pos + n > limit) { + throw new IndexOutOfBoundsException("Cannot consume " + n + " bytes, only " + (limit - pos) + " available"); + } + pos += n; + } + + /** + * Ensures at least {@code n} bytes are available in the buffer. + * + *

      If fewer than {@code n} bytes are currently buffered, this method compacts + * the buffer (moves remaining data to the front) and reads more data from the + * underlying stream until at least {@code n} bytes are available or EOF is reached. + * + *

      After this method returns true, the caller can safely read {@code n} bytes + * directly from {@link #buffer()} starting at {@link #position()}. + * + * @param n the minimum number of bytes required + * @return true if at least n bytes are now available, false if EOF was reached + * @throws IOException if an I/O error occurs + * @throws IllegalArgumentException if n > buffer size + */ + public boolean ensure(int n) throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + if (n > buf.length) { + throw new IllegalArgumentException("Cannot ensure " + n + " bytes, buffer size is " + buf.length); + } + if (n <= 0) { + return true; + } + + int avail = limit - pos; + if (avail >= n) { + return true; + } + + // Compact: move remaining data to front of buffer + if (pos > 0 && avail > 0) { + System.arraycopy(buf, pos, buf, 0, avail); + } + pos = 0; + limit = avail; + + // Fill until we have enough or hit EOF + while (limit < n) { + int read = in.read(buf, limit, buf.length - limit); + if (read < 0) { + return false; // EOF before we could get n bytes + } + limit += read; + } + + return true; + } + /** * Reads a line terminated by CRLF or LF into the provided buffer. * diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java index d758b8caa..ac4345056 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java @@ -8,31 +8,61 @@ import java.io.IOException; import java.util.List; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import software.amazon.smithy.java.http.client.h2.H2Connection; /** - * Manages HTTP/2 connections for multiplexing. + * Manages HTTP/2 connections with adaptive load balancing. * - *

      Uses a per-route state object containing a lock and volatile connection array. - * Fast path (acquire existing connection) requires no locking - just a volatile read - * and array scan. Slow path (create new connection) synchronizes on per-route lock. + *

      Load Balancing Strategy

      + *

      Uses a two-tier "watermark" strategy to distribute streams across connections: + * + *

        + *
      1. Green Zone (streams < soft limit): Uses atomic round-robin to distribute + * requests evenly. Each caller atomically increments an index to get a unique starting + * position, ensuring fair distribution even under high concurrency.
      2. + *
      3. Expansion: When all connections exceed the soft limit, creates a new connection + * (if under the maximum). This prevents overloading a single TCP connection.
      4. + *
      5. Red Zone (at max connections): Uses least-loaded selection to find the + * connection with the fewest active streams, up to the hard limit.
      6. + *
      7. Saturation: When all connections are at the hard limit, callers wait + * for capacity with a configurable timeout.
      8. + *
      + * + *

      Threading

      + *

      Uses per-route state with a volatile connection array for lock-free reads in the + * common case. Connection creation and removal synchronize on the per-route state object. + * The round-robin index uses {@link AtomicInteger} for visibility and atomicity across + * thousands of concurrent virtual threads. */ final class H2ConnectionManager { /** - * Per-route state: synchronize on this object for mutations, volatile array for lock-free reads. + * Per-route connection state. */ private static final class RouteState { + /** Connections for this route. Volatile for lock-free reads. */ volatile H2Connection[] conns = new H2Connection[0]; - // Number of connections currently being created (to prevent over-creation) + + /** Connections currently being created (prevents over-creation). Guarded by sync on this. */ int pendingCreations = 0; + + /** Round-robin index for connection selection. Atomic for concurrent access. */ + final AtomicInteger nextIndex = new AtomicInteger(0); } private static final H2Connection[] EMPTY = new H2Connection[0]; + // Soft limit as a fraction of streamsPerConnection. When all connections exceed this threshold, + // we try to create a new connection (if under max). + // This prevents overloading a single TCP connection even when the server allows many streams. + private static final int SOFT_LIMIT_DIVISOR = 8; + private static final int SOFT_LIMIT_FLOOR = 50; + private final ConcurrentHashMap routes = new ConcurrentHashMap<>(); - private final int streamsPerConnection; + private final int streamsPerConnection; // Hard limit from server + private final int softConcurrencyLimit; // Soft limit for load balancing private final long acquireTimeoutMs; private final List listeners; private final ConnectionFactory connectionFactory; @@ -49,6 +79,7 @@ interface ConnectionFactory { ConnectionFactory connectionFactory ) { this.streamsPerConnection = streamsPerConnection; + this.softConcurrencyLimit = Math.max(SOFT_LIMIT_FLOOR, streamsPerConnection / SOFT_LIMIT_DIVISOR); this.acquireTimeoutMs = acquireTimeoutMs; this.listeners = listeners; this.connectionFactory = connectionFactory; @@ -59,45 +90,46 @@ private RouteState stateFor(Route route) { } /** - * Acquire an H2 connection for the route, creating one if needed. + * Acquire an HTTP/2 connection for the route, creating one if needed. + * + *

      Connection creation happens outside the synchronized block to prevent deadlock + * when connection establishment blocks on I/O. * - *

      Connection creation happens OUTSIDE the synchronized block to prevent deadlock. - * The deadlock scenario: Thread A holds RouteState lock while creating a connection, - * which blocks waiting for a permit. Thread B tries to release a permit but first - * needs to unregister, which requires the RouteState lock held by A. + * @param route the target route + * @param maxConnectionsForRoute maximum connections allowed for this route + * @return an H2 connection ready for use + * @throws IOException if acquisition times out or is interrupted */ H2Connection acquire(Route route, int maxConnectionsForRoute) throws IOException { RouteState state = stateFor(route); long deadline = System.currentTimeMillis() + acquireTimeoutMs; - boolean shouldCreate = false; synchronized (state) { while (true) { H2Connection[] snapshot = state.conns; int totalConns = snapshot.length + state.pendingCreations; - // Try to find a connection under the soft limit - H2Connection conn = tryAcquireUnderLimit(snapshot); + // Green zone: round-robin among connections under soft limit + H2Connection conn = tryAcquireRoundRobin(snapshot, state, softConcurrencyLimit); if (conn != null) { notifyAcquire(conn, true); return conn; } - // All connections at/above soft limit - create new if under connection limit + // Expansion: all connections above soft limit, create new if allowed if (totalConns < maxConnectionsForRoute) { state.pendingCreations++; - shouldCreate = true; break; } - // At connection limit - use any connection even if over soft limit - conn = tryAcquire(snapshot); + // Red zone: at max connections, use least-loaded up to hard limit + conn = tryAcquireLeastLoaded(snapshot, streamsPerConnection); if (conn != null) { notifyAcquire(conn, true); return conn; } - // Wait for capacity + // Saturation: all connections at hard limit, wait for capacity long remaining = deadline - System.currentTimeMillis(); if (remaining <= 0) { throw new IOException("Acquire timeout: no connection available after " @@ -113,10 +145,7 @@ H2Connection acquire(Route route, int maxConnectionsForRoute) throws IOException } } - if (shouldCreate) { - return createNewH2Connection(route, state); - } - throw new IllegalStateException("unreachable"); + return createNewH2Connection(route, state); } @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") @@ -152,64 +181,60 @@ private H2Connection createNewH2Connection(Route route, RouteState state) throws } /** - * Find a reusable connection, preferring low stream count. - * - *

      Single pass: tracks best candidate (lowest active streams under limit), - * falls back to any valid connection if none under limit. + * Round-robin selection: find a connection under the limit, starting from a unique index. * - *

      Note: getActiveStreamCountIfAccepting() checks active state, write errors, and muxer capacity, - * returning the stream count in a single call to avoid redundant atomic reads. - * Socket health is monitored by the reader thread, so separate validateForReuse() is not - * needed in this hot path. + *

      Each caller atomically claims a starting index, then scans connections from that + * position. This ensures even distribution under high concurrency, each gets a different starting point rather + * than all hammering connection[0]. */ - private H2Connection tryAcquire(H2Connection[] conns) { - H2Connection best = null; - int bestActive = Integer.MAX_VALUE; + private H2Connection tryAcquireRoundRobin(H2Connection[] conns, RouteState state, int limit) { + int n = conns.length; + if (n == 0) { + return null; + } - for (H2Connection conn : conns) { + // Mask with MAX_VALUE handles overflow when counter wraps to negative. + int start = (state.nextIndex.getAndIncrement() & Integer.MAX_VALUE) % n; + + for (int i = 0; i < n; i++) { + int idx = (start + i) % n; + H2Connection conn = conns[idx]; if (conn == null) { continue; } - int active = conn.getActiveStreamCountIfAccepting(); - if (active < 0) { - continue; - } - - if (active < streamsPerConnection) { - // Prefer lowest active count - if (active < bestActive) { - best = conn; - bestActive = active; - if (active == 0) { - break; // Can't do better than idle - } - } - } else if (best == null) { - // Fallback: any valid connection - best = conn; + if (active >= 0 && active < limit) { + return conn; } } - - return best; + return null; } /** - * Find a connection strictly under the soft limit. + * Least-loaded selection: find the connection with the fewest active streams. + * + *

      Scans all connections to find the best candidate. Used in the red zone when all connections exceed the + * soft limit and we need to balance the load. */ - private H2Connection tryAcquireUnderLimit(H2Connection[] conns) { + private H2Connection tryAcquireLeastLoaded(H2Connection[] conns, int limit) { + H2Connection best = null; + int bestActive = Integer.MAX_VALUE; + for (H2Connection conn : conns) { if (conn == null) { continue; } - // getActiveStreamCountIfAccepting() checks: active, writeError, muxer capacity - // and returns the count in a single call to avoid redundant atomic reads int active = conn.getActiveStreamCountIfAccepting(); - if (active >= 0 && active < streamsPerConnection) { - return conn; + if (active >= 0 && active < limit && active < bestActive) { + best = conn; + bestActive = active; + if (active == 0) { + break; + } } } - return null; + + return best; } /** @@ -246,9 +271,6 @@ void unregister(Route route, H2Connection conn) { } } - /** - * Remove dead or exhausted connections for the route. - */ void cleanupDead(Route route, BiConsumer onRemove) { RouteState state = routes.get(route); if (state == null) { @@ -296,9 +318,6 @@ void cleanupDead(Route route, BiConsumer onRemove) { } } - /** - * Clean up dead connections for all routes. - */ void cleanupAllDead(BiConsumer onRemove) { for (Route route : routes.keySet()) { cleanupDead(route, onRemove); @@ -306,16 +325,13 @@ void cleanupAllDead(BiConsumer onRemove) { } /** - * Clean up idle connections that have no active streams and have been idle - * longer than the specified timeout. + * Clean up idle connections that have no active streams and have been idle longer than the specified timeout. * * @param maxIdleTimeNanos maximum idle time in nanoseconds - * @param onRemove callback for removed connections - * @return number of connections removed + * @param onRemove callback for removed connections */ @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") - int cleanupIdle(long maxIdleTimeNanos, BiConsumer onRemove) { - int removed = 0; + void cleanupIdle(long maxIdleTimeNanos, BiConsumer onRemove) { for (RouteState state : routes.values()) { H2Connection[] cur = state.conns; @@ -343,7 +359,6 @@ int cleanupIdle(long maxIdleTimeNanos, BiConsumer onR } if (conn.getIdleTimeNanos() > maxIdleTimeNanos) { onRemove.accept(conn, CloseReason.IDLE_TIMEOUT); - removed++; } else { tmp[w++] = conn; } @@ -355,12 +370,8 @@ int cleanupIdle(long maxIdleTimeNanos, BiConsumer onR } } } - return removed; } - /** - * Close all connections. - */ void closeAll(BiConsumer onClose) { for (RouteState state : routes.values()) { H2Connection[] snapshot = state.conns; diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java index 8d596a99c..d890c3faa 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java @@ -30,44 +30,21 @@ *

    1. Proxy tunneling (HTTP and HTTPS proxies)
    2. *
    3. Protocol selection (HTTP/1.1 vs HTTP/2)
    4. * + * + * @param sslParameters may be null */ -final class HttpConnectionFactory { - - private final Duration connectTimeout; - private final Duration tlsNegotiationTimeout; - private final Duration readTimeout; - private final Duration writeTimeout; - private final SSLContext sslContext; - private final SSLParameters sslParameters; // may be null - private final HttpVersionPolicy versionPolicy; - private final DnsResolver dnsResolver; - private final HttpSocketFactory socketFactory; - private final int h2InitialWindowSize; - - HttpConnectionFactory( - Duration connectTimeout, - Duration tlsNegotiationTimeout, - Duration readTimeout, - Duration writeTimeout, - SSLContext sslContext, - SSLParameters sslParameters, - HttpVersionPolicy versionPolicy, - DnsResolver dnsResolver, - HttpSocketFactory socketFactory, - int h2InitialWindowSize - ) { - this.connectTimeout = connectTimeout; - this.tlsNegotiationTimeout = tlsNegotiationTimeout; - this.readTimeout = readTimeout; - this.writeTimeout = writeTimeout; - this.sslContext = sslContext; - this.sslParameters = sslParameters; - this.versionPolicy = versionPolicy; - this.dnsResolver = dnsResolver; - this.socketFactory = socketFactory; - this.h2InitialWindowSize = h2InitialWindowSize; - } - +record HttpConnectionFactory( + Duration connectTimeout, + Duration tlsNegotiationTimeout, + Duration readTimeout, + Duration writeTimeout, + SSLContext sslContext, + SSLParameters sslParameters, + HttpVersionPolicy versionPolicy, + DnsResolver dnsResolver, + HttpSocketFactory socketFactory, + int h2InitialWindowSize, + int h2MaxFrameSize) { /** * Create a new connection to the given route. * @@ -178,7 +155,7 @@ private HttpConnection createProtocolConnection(Socket socket, Route route) thro try { if ("h2".equals(protocol) || "h2c".equals(protocol)) { - return new H2Connection(socket, route, readTimeout, writeTimeout, h2InitialWindowSize); + return new H2Connection(socket, route, readTimeout, writeTimeout, h2InitialWindowSize, h2MaxFrameSize); } else { return new H1Connection(socket, route, readTimeout); } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java index d90c5a32c..9536770c5 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java @@ -180,7 +180,8 @@ public final class HttpConnectionPool implements ConnectionPool { builder.versionPolicy, dnsResolver, builder.socketFactory, - builder.h2InitialWindowSize); + builder.h2InitialWindowSize, + builder.h2MaxFrameSize); this.h1Manager = new H1ConnectionManager(this.maxIdleTimeNanos); this.connectionPermits = new Semaphore(builder.maxTotalConnections, false); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java index 89d1a5849..59281ad1e 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java @@ -25,6 +25,7 @@ public final class HttpConnectionPoolBuilder { int maxConnectionsPerRoute = 20; int h2StreamsPerConnection = 100; int h2InitialWindowSize = 65535; // RFC 9113 default + int h2MaxFrameSize = 16384; // RFC 9113 default final Map perHostLimits = new HashMap<>(); Duration maxIdleTime = Duration.ofMinutes(2); @@ -409,6 +410,37 @@ public HttpConnectionPoolBuilder h2InitialWindowSize(int windowSize) { return this; } + /** + * Set HTTP/2 maximum frame size for receiving DATA frames (default: 16384 bytes). + * + *

      This controls the SETTINGS_MAX_FRAME_SIZE advertised to the server, + * which determines the maximum size of DATA frames the server can send. + * Larger frames reduce per-frame overhead and can improve throughput for + * large response bodies. + * + *

      Performance considerations: + *

        + *
      • Default (16384): RFC 9113 minimum, maximum compatibility
      • + *
      • 65536 (64KB): Good balance of throughput and memory
      • + *
      • 262144 (256KB): Better for large downloads, reduces frame overhead
      • + *
      + * + *

      Note: The actual frame size used depends on the server respecting + * this setting. Some servers may send smaller frames regardless. + * + * @param frameSize maximum frame size in bytes, must be between 16384 and 16777215 + * @return this builder + * @throws IllegalArgumentException if frameSize is not in valid range + */ + public HttpConnectionPoolBuilder h2MaxFrameSize(int frameSize) { + if (frameSize < 16384 || frameSize > 16777215) { + throw new IllegalArgumentException( + "h2MaxFrameSize must be between 16384 and 16777215: " + frameSize); + } + this.h2MaxFrameSize = frameSize; + return this; + } + /** * Set maximum concurrent streams per HTTP/2 connection before creating a new connection (default: 100). * diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/DataChunk.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/DataChunk.java new file mode 100644 index 000000000..e17b3b3c4 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/DataChunk.java @@ -0,0 +1,11 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +/** + * Data chunk containing a buffer and metadata. + */ +record DataChunk(byte[] data, int length, boolean endStream) {} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java index cc58cd8f4..77f9e9762 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java @@ -5,18 +5,20 @@ package software.amazon.smithy.java.http.client.h2; -import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; /** - * HTTP/2 flow control window backed by a Semaphore. + * HTTP/2 flow control window. * - *

      Wraps acquire/release semantics for flow control with Virtual Thread-friendly - * blocking. Uses an unfair semaphore for throughput over ordering. + *

      Uses ReentrantLock instead of synchronized to avoid virtual thread pinning. */ final class FlowControlWindow { - private final Semaphore permits; + private final ReentrantLock lock = new ReentrantLock(); + private final Condition available = lock.newCondition(); + private long window; /** * Create a flow control window. @@ -24,58 +26,91 @@ final class FlowControlWindow { * @param initialWindow the initial window size (e.g., 65535 for HTTP/2 default) */ FlowControlWindow(int initialWindow) { - // Use unfair semaphore for better throughput (no FIFO ordering overhead) - this.permits = new Semaphore(initialWindow, false); + this.window = initialWindow; } /** - * Acquire permits with a timeout. + * Try to acquire bytes from the window with a timeout. * * @param bytes number of bytes to acquire - * @param timeout maximum time to wait - * @param unit time unit for timeout - * @return true if permits acquired, false if timeout expired + * @param timeoutMs maximum time to wait in milliseconds + * @return true if bytes acquired, false if timeout expired * @throws InterruptedException if interrupted while waiting */ - boolean tryAcquire(int bytes, long timeout, TimeUnit unit) throws InterruptedException { - return permits.tryAcquire(bytes, timeout, unit); + boolean tryAcquire(int bytes, long timeoutMs) throws InterruptedException { + lock.lock(); + try { + // Fast path: no waiting needed + if (window >= bytes) { + window -= bytes; + return true; + } + + // Slow path: wait with timeout + long remainingNs = TimeUnit.MILLISECONDS.toNanos(timeoutMs); + while (window < bytes) { + if (remainingNs <= 0) { + return false; + } + remainingNs = available.awaitNanos(remainingNs); + } + + window -= bytes; + return true; + } finally { + lock.unlock(); + } } /** - * Release permits back to the window. + * Release bytes back to the window. * * @param bytes number of bytes to release */ void release(int bytes) { if (bytes > 0) { - permits.release(bytes); + lock.lock(); + try { + window += bytes; + available.signalAll(); + } finally { + lock.unlock(); + } } } /** * Get the current available window size. * - * @return available bytes in the window + * @return available bytes in the window (may be negative if window was shrunk) */ int available() { - return permits.availablePermits(); + lock.lock(); + try { + return (int) Math.min(window, Integer.MAX_VALUE); + } finally { + lock.unlock(); + } } /** * Adjust the window size (e.g., when SETTINGS changes initial window). * - *

      This can increase or decrease the window. If decreasing and the new window would be negative, this may - * cause subsequent acquires to block until the window recovers. + *

      This can increase or decrease the window. If decreasing, the window + * may become negative (valid in HTTP/2), and writers will block until + * WINDOW_UPDATE frames restore capacity. * * @param delta change in window size (positive or negative) */ void adjust(int delta) { - if (delta > 0) { - permits.release(delta); - } else if (delta < 0) { - // Reducing window: try to acquire the permits. If not enough available, the window goes "into debt" - // and future acquires will block longer. - var _ignored = permits.tryAcquire(-delta); + lock.lock(); + try { + window += delta; + if (delta > 0) { + available.signalAll(); + } + } finally { + lock.unlock(); } } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java index 6a7f3d776..d86695aa6 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.http.client.h2; -import static software.amazon.smithy.java.http.client.h2.H2Constants.CONNECTION_PREFACE; import static software.amazon.smithy.java.http.client.h2.H2Constants.DEFAULT_HEADER_TABLE_SIZE; import static software.amazon.smithy.java.http.client.h2.H2Constants.DEFAULT_INITIAL_WINDOW_SIZE; import static software.amazon.smithy.java.http.client.h2.H2Constants.DEFAULT_MAX_CONCURRENT_STREAMS; @@ -42,7 +41,7 @@ import java.net.SocketTimeoutException; import java.time.Duration; import java.util.List; -import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import software.amazon.smithy.java.http.api.HttpRequest; @@ -87,12 +86,10 @@ private enum State { private static final InternalLogger LOGGER = InternalLogger.getLogger(H2Connection.class); private static final int BUFFER_SIZE = 65536; - private static final byte[] EMPTY_PAYLOAD = new byte[0]; private static final int SETTINGS_TIMEOUT_MS = 10_000; private static final int GRACEFUL_SHUTDOWN_MS = 1000; private final Socket socket; - private final UnsyncBufferedOutputStream socketOut; private final Route route; private final H2FrameCodec frameCodec; private final H2Muxer muxer; @@ -100,26 +97,25 @@ private enum State { private final Thread readerThread; private final long readTimeoutMs; private final long writeTimeoutMs; + private final int maxFrameSize; // Connection settings from peer private volatile int remoteMaxFrameSize = DEFAULT_MAX_FRAME_SIZE; private volatile int remoteInitialWindowSize = DEFAULT_INITIAL_WINDOW_SIZE; private volatile int remoteMaxConcurrentStreams = DEFAULT_MAX_CONCURRENT_STREAMS; - private volatile int remoteHeaderTableSize = DEFAULT_HEADER_TABLE_SIZE; private volatile int remoteMaxHeaderListSize = Integer.MAX_VALUE; // Connection receive window (send window is managed by muxer). Only accessed by reader thread. private int connectionRecvWindow; private final int initialWindowSize; - // Connection state - private volatile State state = State.CONNECTED; + // Connection state (AtomicReference for safe concurrent close) + private final AtomicReference state = new AtomicReference<>(State.CONNECTED); private volatile boolean active = true; private volatile boolean goawayReceived = false; - private volatile int goawayLastStreamId = Integer.MAX_VALUE; private volatile Throwable readerError; - // Track last activity time for idle timeout (nanos) - private volatile long lastActivityTimeNanos = System.nanoTime(); + // Track last activity tick for idle timeout (tick = TIMEOUT_POLL_INTERVAL_MS, ~100ms resolution) + private volatile int lastActivityTick; /** * Create an HTTP/2 connection from a connected socket. @@ -129,16 +125,24 @@ private enum State { * @param readTimeout read timeout duration * @param writeTimeout write timeout duration * @param initialWindowSize initial flow control window size in bytes + * @param maxFrameSize maximum frame size to advertise to server */ - public H2Connection(Socket socket, Route route, Duration readTimeout, Duration writeTimeout, int initialWindowSize) - throws IOException { + public H2Connection( + Socket socket, + Route route, + Duration readTimeout, + Duration writeTimeout, + int initialWindowSize, + int maxFrameSize + ) throws IOException { this.socket = socket; + this.maxFrameSize = maxFrameSize; var socketIn = new UnsyncBufferedInputStream(socket.getInputStream(), BUFFER_SIZE); - this.socketOut = new UnsyncBufferedOutputStream(socket.getOutputStream(), BUFFER_SIZE); + var socketOut = new UnsyncBufferedOutputStream(socket.getOutputStream(), BUFFER_SIZE); this.route = route; this.readTimeoutMs = readTimeout.toMillis(); this.writeTimeoutMs = writeTimeout.toMillis(); - this.frameCodec = new H2FrameCodec(socketIn, socketOut, DEFAULT_MAX_FRAME_SIZE); + this.frameCodec = new H2FrameCodec(socketIn, socketOut, maxFrameSize); this.hpackDecoder = new HpackDecoder(DEFAULT_HEADER_TABLE_SIZE); this.initialWindowSize = initialWindowSize; this.connectionRecvWindow = initialWindowSize; @@ -160,16 +164,14 @@ public H2Connection(Socket socket, Route route, Duration readTimeout, Duration w } // Start background reader thread - this.readerThread = Thread.ofVirtual() - .name("h2-reader-" + route.host()) - .start(this::readerLoop); + this.readerThread = Thread.ofVirtual().name("h2-reader-" + route.host()).start(this::readerLoop); } // ==================== ConnectionCallback implementation ==================== @Override public boolean isAcceptingStreams() { - return state == State.CONNECTED && !goawayReceived; + return state.get() == State.CONNECTED && !goawayReceived; } @Override @@ -179,47 +181,58 @@ public int getRemoteMaxHeaderListSize() { // ==================== Reader Thread ==================== + // Track last stream for batched signaling (stream-switch detection). + // With lock-free signaling, flushing the previous stream is cheap (just LockSupport.unpark). + private H2Exchange lastDataExchange; + private void readerLoop() { try { - while (state == State.CONNECTED) { - H2FrameCodec.FrameHeader header = frameCodec.readFrameHeader(); - if (header == null) { - break; + while (state.get() == State.CONNECTED) { + int type = frameCodec.nextFrame(); + if (type < 0) { + break; // EOF } - // Update last activity time on every frame received - lastActivityTimeNanos = System.nanoTime(); + // Update last activity tick on every frame received (cheap volatile write vs syscall) + lastActivityTick = muxer.currentTimeoutTick(); - if (header.type() == FRAME_TYPE_DATA) { - handleDataFrame(header); + if (type == FRAME_TYPE_DATA) { + handleDataFrame(); } else { - H2FrameCodec.Frame frame = readNonDataFrame(header); - dispatchFrame(frame); + // Non-DATA frame: flush any pending data stream before processing + if (lastDataExchange != null) { + lastDataExchange.signalDataAvailable(); + lastDataExchange = null; + } + handleNonDataFrame(); } } } catch (IOException e) { - if (state == State.CONNECTED) { + if (state.get() == State.CONNECTED) { readerError = e; active = false; LOGGER.debug("Reader thread error for {}: {}", route, e.getMessage()); } } finally { - if (muxer != null) { - muxer.shutdownNow(); + // Flush any pending stream before shutdown + if (lastDataExchange != null) { + lastDataExchange.signalDataAvailable(); + lastDataExchange = null; } + muxer.shutdownNow(); muxer.onConnectionClosing(readerError); - state = State.CLOSED; + state.set(State.CLOSED); try { socket.close(); } catch (IOException ignored) {} } } - private void handleDataFrame(H2FrameCodec.FrameHeader header) throws IOException { - int streamId = header.streamId(); - int payloadLength = header.payloadLength(); - boolean endStream = header.hasFlag(FLAG_END_STREAM); - boolean padded = header.hasFlag(FLAG_PADDED); + private void handleDataFrame() throws IOException { + int streamId = frameCodec.frameStreamId(); + int payloadLength = frameCodec.framePayloadLength(); + boolean endStream = frameCodec.hasFrameFlag(FLAG_END_STREAM); + boolean padded = frameCodec.hasFrameFlag(FLAG_PADDED); if (streamId == 0) { throw new H2Exception(ERROR_PROTOCOL_ERROR, "DATA frame must have non-zero stream ID"); @@ -227,6 +240,11 @@ private void handleDataFrame(H2FrameCodec.FrameHeader header) throws IOException H2Exchange exchange = muxer.getExchange(streamId); + // Stream switch detection: flush the previous stream if we're switching (lock-free) + if (lastDataExchange != null && lastDataExchange != exchange) { + lastDataExchange.signalDataAvailable(); + } + int padLength = 0; int dataLength = payloadLength; if (padded) { @@ -242,16 +260,18 @@ private void handleDataFrame(H2FrameCodec.FrameHeader header) throws IOException if (exchange != null) { if (dataLength > 0) { - if (!exchange.ensureBufferSpace(dataLength)) { - throw new H2Exception(ERROR_FLOW_CONTROL_ERROR, - streamId, - "Exchange buffer overflow - flow control failure"); - } - frameCodec.readPayloadInto(exchange.getDataBuffer(), exchange.getWritePos(), dataLength); - exchange.commitWrite(dataLength, endStream); + // Borrow byte[] from pool and read payload into it + byte[] buffer = muxer.borrowBuffer(dataLength); + frameCodec.readPayloadInto(buffer, 0, dataLength); + // Check if more data is buffered - used for adaptive signaling to reduce wakeups + boolean moreDataBuffered = frameCodec.hasBufferedData(); + exchange.enqueueData(buffer, dataLength, endStream, moreDataBuffered); consumeConnectionRecvWindow(dataLength); + // Track for stream-switch detection; clear if buffer empty (we just signaled) + lastDataExchange = moreDataBuffered ? exchange : null; } else if (endStream) { - exchange.commitWrite(0, true); + exchange.enqueueData(null, 0, true, false); + lastDataExchange = null; } } else { if (dataLength > 0) { @@ -259,6 +279,10 @@ private void handleDataFrame(H2FrameCodec.FrameHeader header) throws IOException consumeConnectionRecvWindow(dataLength); } LOGGER.trace("Ignoring DATA frame for closed stream {}", streamId); + // Clear tracker if buffer empty (even for unknown streams) + if (!frameCodec.hasBufferedData()) { + lastDataExchange = null; + } } if (padLength > 0) { @@ -266,84 +290,106 @@ private void handleDataFrame(H2FrameCodec.FrameHeader header) throws IOException } } - private H2FrameCodec.Frame readNonDataFrame(H2FrameCodec.FrameHeader header) throws IOException { - int length = header.payloadLength(); + private void handleNonDataFrame() throws IOException { + int type = frameCodec.frameType(); + int streamId = frameCodec.frameStreamId(); + int length = frameCodec.framePayloadLength(); + + // Fast path: handle small control frames (WINDOW_UPDATE, RST_STREAM) without buffer allocation. + // These frames are always exactly 4 bytes and very common during flow control. + if (type == FRAME_TYPE_WINDOW_UPDATE) { + int increment = frameCodec.readAndParseWindowUpdate(); + if (streamId == 0) { + muxer.releaseConnectionWindow(increment); + } else { + H2Exchange exchange = muxer.getExchange(streamId); + if (exchange != null) { + exchange.updateStreamSendWindow(increment); + } + // Ignore WINDOW_UPDATE for unknown streams (closed streams, etc.) + } + return; + } + + if (type == FRAME_TYPE_RST_STREAM && streamId != 0) { + int errorCode = frameCodec.readAndParseRstStream(); + H2Exchange exchange = muxer.getExchange(streamId); + if (exchange != null) { + H2Exception error = new H2Exception(errorCode, + streamId, + "Stream reset by server: " + H2Constants.errorCodeName(errorCode)); + exchange.signalStreamError(error); + } + // Ignore RST_STREAM for unknown streams + return; + } + + // Standard path: read payload into pooled buffer byte[] payload; if (length == 0) { - payload = EMPTY_PAYLOAD; + payload = H2Constants.EMPTY_BYTES; } else { - payload = new byte[length]; + payload = muxer.borrowBuffer(length); frameCodec.readPayloadInto(payload, 0, length); } - return new H2FrameCodec.Frame(header.type(), header.flags(), header.streamId(), payload, length); - } - private void dispatchFrame(H2FrameCodec.Frame frame) throws IOException { - int streamId = frame.streamId(); + try { + if (streamId == 0) { + handleConnectionFrame(type, payload, length); + } else { + // Handle HEADERS with CONTINUATION frames + byte[] headerPayload = payload; + int headerLength = length; + if (type == FRAME_TYPE_HEADERS && !frameCodec.hasFrameFlag(FLAG_END_HEADERS)) { + headerPayload = frameCodec.readHeaderBlock(streamId, payload, length); + headerLength = frameCodec.headerBlockSize(); + // Return original payload, headerPayload is a view into frameCodec's buffer + if (payload != H2Constants.EMPTY_BYTES) { + muxer.returnBuffer(payload); + } + payload = null; // Mark as already returned + } - if (streamId == 0) { - handleConnectionFrame(frame); - } else { - if (frame.type() == FRAME_TYPE_HEADERS && !frame.hasFlag(FLAG_END_HEADERS)) { - byte[] headerBlock = frameCodec.readHeaderBlock(frame); - frame = new H2FrameCodec.Frame( - FRAME_TYPE_HEADERS, - frame.flags() | FLAG_END_HEADERS, - streamId, - headerBlock, - headerBlock.length); + H2Exchange exchange = muxer.getExchange(streamId); + if (exchange != null) { + dispatchStreamFrame(exchange, type, streamId, headerPayload, headerLength); + } else { + handleFrameForUnknownStream(type, streamId, headerPayload, headerLength); + } } - - H2Exchange exchange = muxer.getExchange(streamId); - if (exchange != null) { - dispatchStreamFrame(exchange, frame, streamId); - } else { - handleFrameForUnknownStream(frame, streamId); + } finally { + // Return pooled buffer (only if not already returned) + if (payload != null && payload != H2Constants.EMPTY_BYTES) { + muxer.returnBuffer(payload); } } } - private void handleFrameForUnknownStream(H2FrameCodec.Frame frame, int streamId) throws IOException { - if (frame.type() == FRAME_TYPE_DATA) { - byte[] payload = frame.payload(); - if (payload != null && payload.length > 0) { - consumeConnectionRecvWindow(payload.length); + private void handleFrameForUnknownStream(int type, int streamId, byte[] payload, int length) throws IOException { + // Note: DATA frames for unknown streams are handled in handleDataFrame(), not here. + if (type == FRAME_TYPE_HEADERS) { + // Must decode headers to maintain HPACK state, even for unknown streams + if (payload != null && length > 0) { + decodeHeaders(payload, length); } - LOGGER.trace("Ignoring DATA frame for closed stream {}", streamId); - } else if (frame.type() == FRAME_TYPE_HEADERS) { - byte[] headerBlock = frame.payload(); - if (headerBlock != null && headerBlock.length > 0) { - decodeHeaders(headerBlock); - } - LOGGER.trace("Ignoring HEADERS frame for closed stream {}", streamId); + LOGGER.trace("Ignoring HEADERS frame for unknown stream {}", streamId); } } - private void dispatchStreamFrame(H2Exchange exchange, H2FrameCodec.Frame frame, int streamId) + private void dispatchStreamFrame(H2Exchange exchange, int type, int streamId, byte[] payload, int length) throws IOException { - switch (frame.type()) { + // Note: WINDOW_UPDATE and RST_STREAM are handled in handleNonDataFrame fast path + switch (type) { case FRAME_TYPE_HEADERS -> { - byte[] headerBlock = frame.payload(); List decoded; - if (headerBlock != null && headerBlock.length > 0) { - decoded = decodeHeaders(headerBlock); + if (payload != null && length > 0) { + decoded = decodeHeaders(payload, length); } else { decoded = List.of(); } - boolean endStream = frame.hasFlag(FLAG_END_STREAM); + boolean endStream = frameCodec.hasFrameFlag(FLAG_END_STREAM); exchange.deliverHeaders(decoded, endStream); } - case FRAME_TYPE_RST_STREAM -> { - int errorCode = frame.parseRstStream(); - H2Exception error = new H2Exception(errorCode, - streamId, - "Stream reset by server: " + H2Constants.errorCodeName(errorCode)); - exchange.signalStreamError(error); - } - case FRAME_TYPE_WINDOW_UPDATE -> { - int increment = frame.parseWindowUpdate(); - exchange.updateStreamSendWindow(increment); - } case FRAME_TYPE_PUSH_PROMISE -> { throw new H2Exception(ERROR_PROTOCOL_ERROR, "Received PUSH_PROMISE but server push is disabled"); @@ -353,11 +399,13 @@ private void dispatchStreamFrame(H2Exchange exchange, H2FrameCodec.Frame frame, } } - private void handleConnectionFrame(H2FrameCodec.Frame frame) throws IOException { - switch (frame.type()) { + private void handleConnectionFrame(int type, byte[] payload, int length) throws IOException { + // Note: WINDOW_UPDATE is handled in handleNonDataFrame fast path + switch (type) { case FRAME_TYPE_SETTINGS -> { - if (!frame.hasFlag(FLAG_ACK)) { - applyRemoteSettings(frame); + if (!frameCodec.hasFrameFlag(FLAG_ACK)) { + int[] settings = frameCodec.parseSettings(payload, length); + applyRemoteSettings(settings); muxer.queueControlFrame(0, H2Muxer.ControlFrameType.SETTINGS_ACK, null, @@ -365,21 +413,20 @@ private void handleConnectionFrame(H2FrameCodec.Frame frame) throws IOException } } case FRAME_TYPE_PING -> { - if (!frame.hasFlag(FLAG_ACK)) { + if (!frameCodec.hasFrameFlag(FLAG_ACK)) { + // Copy payload for async write (payload may be pooled buffer) + byte[] pingData = new byte[8]; + System.arraycopy(payload, 0, pingData, 0, 8); muxer.queueControlFrame(0, H2Muxer.ControlFrameType.PING, - frame.payload(), + pingData, writeTimeoutMs); } } case FRAME_TYPE_GOAWAY -> { - int[] goaway = frame.parseGoaway(); + int[] goaway = frameCodec.parseGoaway(payload, length); handleGoaway(goaway[0], goaway[1]); } - case FRAME_TYPE_WINDOW_UPDATE -> { - int increment = frame.parseWindowUpdate(); - muxer.releaseConnectionWindow(increment); - } default -> { } } @@ -388,14 +435,14 @@ private void handleConnectionFrame(H2FrameCodec.Frame frame) throws IOException // ==================== Connection Preface ==================== private void sendConnectionPreface() throws IOException { - socketOut.write(CONNECTION_PREFACE); + frameCodec.writeConnectionPreface(); frameCodec.writeSettings( SETTINGS_MAX_CONCURRENT_STREAMS, 100, SETTINGS_INITIAL_WINDOW_SIZE, initialWindowSize, SETTINGS_MAX_FRAME_SIZE, - 16384, + maxFrameSize, SETTINGS_ENABLE_PUSH, 0); frameCodec.flush(); @@ -414,25 +461,36 @@ private void receiveServerPreface() throws IOException { try { socket.setSoTimeout(SETTINGS_TIMEOUT_MS); - H2FrameCodec.Frame frame; + int type; try { - frame = frameCodec.readFrame(); + type = frameCodec.nextFrame(); } catch (SocketTimeoutException e) { throw new H2Exception(ERROR_SETTINGS_TIMEOUT, "Timeout waiting for server SETTINGS frame"); } - if (frame == null) { + if (type < 0) { throw new IOException("Connection closed before receiving server SETTINGS"); } - if (frame.type() != FRAME_TYPE_SETTINGS) { + if (type != FRAME_TYPE_SETTINGS) { throw new H2Exception(ERROR_PROTOCOL_ERROR, - "Expected SETTINGS frame, got " + H2Constants.frameTypeName(frame.type())); + "Expected SETTINGS frame, got " + H2Constants.frameTypeName(type)); } - if (frame.hasFlag(FLAG_ACK)) { + if (frameCodec.hasFrameFlag(FLAG_ACK)) { throw new H2Exception(ERROR_PROTOCOL_ERROR, "First SETTINGS frame must not be ACK"); } - applyRemoteSettings(frame); + // Read and parse settings payload + int length = frameCodec.framePayloadLength(); + byte[] payload; + if (length == 0) { + payload = H2Constants.EMPTY_BYTES; + } else { + payload = new byte[length]; + frameCodec.readPayloadInto(payload, 0, length); + } + int[] settings = frameCodec.parseSettings(payload, length); + applyRemoteSettings(settings); + frameCodec.writeSettingsAck(); frameCodec.flush(); } finally { @@ -440,15 +498,13 @@ private void receiveServerPreface() throws IOException { } } - private void applyRemoteSettings(H2FrameCodec.Frame frame) throws IOException { - int[] settings = frame.parseSettings(); + private void applyRemoteSettings(int[] settings) throws IOException { for (int i = 0; i < settings.length; i += 2) { int id = settings[i]; int value = settings[i + 1]; switch (id) { case SETTINGS_HEADER_TABLE_SIZE: - remoteHeaderTableSize = value; muxer.setMaxTableSize(value); break; case SETTINGS_ENABLE_PUSH: @@ -485,12 +541,12 @@ private void applyRemoteSettings(H2FrameCodec.Frame frame) throws IOException { @Override public HttpExchange newExchange(HttpRequest request) throws IOException { - if (state != State.CONNECTED) { - throw new IOException("Connection is not in CONNECTED state: " + state); + if (state.get() != State.CONNECTED) { + throw new IOException("Connection is not in CONNECTED state: " + state.get()); } - // Update last activity time when creating a new exchange - lastActivityTimeNanos = System.nanoTime(); + // Update last activity tick when creating a new exchange + lastActivityTick = muxer.currentTimeoutTick(); H2Exchange exchange = muxer.newExchange(request, readTimeoutMs, writeTimeoutMs); @@ -498,46 +554,21 @@ public HttpExchange newExchange(HttpRequest request) throws IOException { boolean hasBody = request.body() != null && request.body().contentLength() != 0; boolean endStream = !hasBody; - CompletableFuture streamIdFuture = new CompletableFuture<>(); - CompletableFuture writeComplete = new CompletableFuture<>(); - - if (!muxer.submitHeaders(request, - exchange, - endStream, - streamIdFuture, - writeComplete, - writeTimeoutMs)) { + if (!muxer.submitHeaders(request, exchange, endStream, writeTimeoutMs)) { muxer.releaseStreamSlot(); - throw new IOException( - "Write queue full - connection overloaded (timeout after " + writeTimeoutMs + "ms)"); + throw new IOException("Connection not accepting new streams"); } - int streamId; - try { - streamId = streamIdFuture.join(); - } catch (Exception e) { - muxer.releaseStreamSlot(); - Throwable cause = e.getCause(); - if (cause instanceof IOException ioe) { - throw ioe; - } - throw new IOException("Encoding failed", cause != null ? cause : e); - } - - try { - writeComplete.join(); - } catch (Exception e) { - muxer.releaseStream(streamId); - Throwable cause = e.getCause(); - if (cause instanceof IOException ioe) { - throw ioe; - } - throw new IOException("Failed to write headers", cause != null ? cause : e); - } + // Block until headers are encoded and written + // Stream ID is set on the exchange by the muxer before signaling + exchange.awaitWriteCompletion(); IOException writeErr = muxer.getWriteError(); if (writeErr != null) { - muxer.releaseStream(streamId); + int streamId = exchange.getStreamId(); + if (streamId > 0) { + muxer.releaseStream(streamId); + } throw writeErr; } @@ -565,16 +596,7 @@ public int getActiveStreamCount() { * and muxer capacity checks to minimize redundant checks. */ public boolean canAcceptMoreStreams() { - // Fast check: if not active, don't bother with other checks - if (!active) { - return false; - } else if (muxer.getWriteError() != null) { - // found write errors (updated by writer thread on failure) - return false; - } else { - // Check muxer capacity - return muxer.canAcceptMoreStreams(); - } + return active && muxer.getWriteError() == null && muxer.canAcceptMoreStreams(); } /** @@ -583,9 +605,7 @@ public boolean canAcceptMoreStreams() { * in the connection acquisition hot path. */ public int getActiveStreamCountIfAccepting() { - if (!active) { - return -1; - } else if (muxer.getWriteError() != null) { + if (!active || muxer.getWriteError() != null) { return -1; } return muxer.getActiveStreamCountIfAccepting(); @@ -597,13 +617,17 @@ public int getActiveStreamCountIfAccepting() { *

      If there are active streams, returns 0 (not idle). * Otherwise, returns the time since the last frame was received or exchange was created. * + *

      Note: Resolution is ~100ms (tick-based) to avoid System.nanoTime() syscalls on hot path. + * * @return idle time in nanoseconds, or 0 if there are active streams */ public long getIdleTimeNanos() { if (getActiveStreamCount() > 0) { return 0; // Not idle if there are active streams } - return System.nanoTime() - lastActivityTimeNanos; + int idleTicks = muxer.currentTimeoutTick() - lastActivityTick; + // Convert ticks to nanoseconds: ticks * ms_per_tick * nanos_per_ms + return (long) idleTicks * H2Muxer.TIMEOUT_POLL_INTERVAL_MS * 1_000_000L; } @Override @@ -629,7 +653,7 @@ public boolean validateForReuse() { if (writeErr != null) { LOGGER.debug("Connection to {} has write error", route); active = false; - state = State.CLOSED; + state.set(State.CLOSED); return false; } @@ -661,53 +685,40 @@ public String negotiatedProtocol() { @Override public void close() throws IOException { - if (state == State.CLOSED) { + // Check if it's already shutting down or closed + if (!state.compareAndSet(State.CONNECTED, State.SHUTTING_DOWN)) { return; } active = false; - State previousState = state; - state = State.SHUTTING_DOWN; - - if (previousState == State.CONNECTED) { - try { - muxer.queueControlFrame(0, - H2Muxer.ControlFrameType.GOAWAY, - new Object[] {muxer.getLastAllocatedStreamId(), ERROR_NO_ERROR, null}, - 100); // Short timeout for shutdown - } catch (IOException ignored) {} - } - if (muxer != null) { - muxer.close(); - } + // Queue the control frame to shutdown, but use a short timeout + var payload = new Object[] {muxer.getLastAllocatedStreamId(), ERROR_NO_ERROR, null}; + muxer.queueControlFrame(0, H2Muxer.ControlFrameType.GOAWAY, payload, 100); + muxer.close(); muxer.closeExchanges(Duration.ofMillis(GRACEFUL_SHUTDOWN_MS)); - state = State.CLOSED; + state.set(State.CLOSED); socket.close(); - if (readerThread != null) { - try { - readerThread.join(100); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - } + try { + readerThread.join(100); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); } } - // ==================== Helper Methods ==================== - // Called only from reader thread - no synchronization needed - List decodeHeaders(byte[] headerBlock) throws IOException { + List decodeHeaders(byte[] headerBlock, int length) throws IOException { int maxHeaderListSize = H2Constants.DEFAULT_MAX_HEADER_LIST_SIZE; - if (headerBlock.length > maxHeaderListSize) { + if (length > maxHeaderListSize) { throw new H2Exception(ERROR_ENHANCE_YOUR_CALM, - "Header block size " + headerBlock.length + " exceeds limit " + maxHeaderListSize); + "Header block size " + length + " exceeds limit " + maxHeaderListSize); } List headers; try { - headers = hpackDecoder.decodeBlock(headerBlock, 0, headerBlock.length); + headers = hpackDecoder.decodeBlock(headerBlock, 0, length); } catch (IOException e) { active = false; LOGGER.debug("HPACK decoding failed for {}: {}", route, e.getMessage()); @@ -738,23 +749,19 @@ void consumeConnectionRecvWindow(int bytes) throws IOException { if (connectionRecvWindow < initialWindowSize / H2Constants.WINDOW_UPDATE_THRESHOLD_DIVISOR) { int increment = initialWindowSize - connectionRecvWindow; connectionRecvWindow += increment; - muxer.queueControlFrame(0, - H2Muxer.ControlFrameType.WINDOW_UPDATE, - increment, - writeTimeoutMs); + muxer.queueControlFrame(0, H2Muxer.ControlFrameType.WINDOW_UPDATE, increment, writeTimeoutMs); } } void handleGoaway(int lastStreamId, int errorCode) { goawayReceived = true; - goawayLastStreamId = lastStreamId; active = false; if (errorCode != ERROR_NO_ERROR) { LOGGER.debug("Server sent GOAWAY to {}: {}", route, H2Constants.errorCodeName(errorCode)); } - state = State.SHUTTING_DOWN; + state.set(State.SHUTTING_DOWN); muxer.onGoaway(lastStreamId, errorCode); } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java index 34c53a16d..00097274d 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Constants.java @@ -14,7 +14,8 @@ final class H2Constants { private H2Constants() {} - static final int WRITER_QUEUE_CAPACITY = 2048; + // Shared empty byte array to avoid repeated allocations + static final byte[] EMPTY_BYTES = new byte[0]; // Our limit for received header list size (not from server SETTINGS) static final int DEFAULT_MAX_HEADER_LIST_SIZE = 8192; @@ -52,7 +53,6 @@ private H2Constants() {} static final int SETTINGS_INITIAL_WINDOW_SIZE = 0x4; static final int SETTINGS_MAX_FRAME_SIZE = 0x5; static final int SETTINGS_MAX_HEADER_LIST_SIZE = 0x6; - static final int SETTINGS_NO_RFC7540_PRIORITIES = 0x9; // RFC 9113 - disable deprecated priority // Default settings values static final int DEFAULT_HEADER_TABLE_SIZE = 4096; @@ -64,9 +64,6 @@ private H2Constants() {} static final int MIN_MAX_FRAME_SIZE = 16384; // 2^14 static final int MAX_MAX_FRAME_SIZE = 16777215; // 2^24 - 1 - // Window size limits - static final int MAX_WINDOW_SIZE = Integer.MAX_VALUE; // 2^31 - 1 - // WINDOW_UPDATE threshold: send update when window drops below this fraction of initial size. // Using 1/3 (33%) reduces control frame overhead while leaving enough buffer to avoid stalls. static final int WINDOW_UPDATE_THRESHOLD_DIVISOR = 3; diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java index c3fa2a869..860da0b27 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java @@ -8,79 +8,182 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.function.Consumer; /** * Input stream for reading response body from DATA frames. * - *

      This implementation reads directly from the exchange's buffer, - * which is filled by the connection's reader thread. This avoids - * per-frame allocations and reduces copying. + *

      This implementation uses batch dequeuing to pull multiple data chunks from the exchange + * in a single lock acquisition, reducing lock contention. The InputStream manages its own + * buffer state (currentBuffer, readPosition) and a local batch of chunks. + * + *

      Buffer lifecycle: + *

        + *
      1. Connection borrows buffer from muxer pool, fills from socket, enqueues chunk to exchange
      2. + *
      3. InputStream drains chunks in batches when local batch is exhausted
      4. + *
      5. InputStream returns exhausted buffer to pool via consumer
      6. + *
      */ final class H2DataInputStream extends InputStream { + /** + * Number of chunks to pull in a single batch. This reduces lock acquisitions by 8x for large responses. + */ + private static final int BATCH_SIZE = 8; - private static final int TRANSFER_BUFFER_SIZE = 16384; private final H2Exchange exchange; + private final Consumer bufferReturner; + private final DataChunk[] localBatch = new DataChunk[BATCH_SIZE]; + private int batchIndex = 0; + private int batchCount = 0; + + // Current buffer state + private byte[] currentBuffer; + private int currentLength; + private int readPosition; + private boolean eof = false; private boolean closed = false; - private byte[] singleBuff; + private final byte[] singleBuff = new byte[1]; - H2DataInputStream(H2Exchange exchange) { + H2DataInputStream(H2Exchange exchange, Consumer bufferReturner) { this.exchange = exchange; + this.bufferReturner = bufferReturner; } @Override public int read() throws IOException { - if (closed) { + if (closed || eof) { return -1; } - if (singleBuff == null) { - singleBuff = new byte[1]; - } - int n = exchange.readFromBuffer(singleBuff, 0, 1); + + int n = read(singleBuff, 0, 1); return n == 1 ? (singleBuff[0] & 0xFF) : -1; } @Override public int read(byte[] b, int off, int len) throws IOException { - if (closed) { + if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (closed || eof) { return -1; } else if (len == 0) { return 0; } - return exchange.readFromBuffer(b, off, len); + // Ensure we have data + if (currentBuffer == null || readPosition >= currentLength) { + if (!pullNextChunk()) { + return -1; // EOF + } + } + + // Copy from current buffer + int available = currentLength - readPosition; + int toCopy = Math.min(available, len); + System.arraycopy(currentBuffer, readPosition, b, off, toCopy); + readPosition += toCopy; + + // Notify exchange of bytes consumed (for flow control) + exchange.onDataConsumed(toCopy); + + return toCopy; + } + + /** + * Pull the next data chunk, using batch dequeuing to reduce lock contention. + * + *

      Chunks are pulled from a local batch first (no lock). When the local batch + * is exhausted, we drain multiple chunks from the exchange in a single lock + * acquisition. + * + * @return true if a chunk was pulled, false if EOF + */ + private boolean pullNextChunk() throws IOException { + // Return previous buffer to pool + if (currentBuffer != null) { + bufferReturner.accept(currentBuffer); + currentBuffer = null; + currentLength = 0; + } + + // Try local batch first (no lock needed) + if (batchIndex >= batchCount) { + // Local batch empty - drain more chunks from exchange (one lock acquisition) + int drained = exchange.drainChunks(localBatch, BATCH_SIZE); + if (drained < 0) { + eof = true; + return false; + } + batchIndex = 0; + batchCount = drained; + } + + DataChunk chunk = localBatch[batchIndex]; + localBatch[batchIndex] = null; + batchIndex++; + + currentBuffer = chunk.data(); + currentLength = chunk.length(); + readPosition = 0; + + return true; } @Override public int available() { - if (closed) { + if (closed || eof) { + return 0; + } else if (currentBuffer == null) { return 0; } - return exchange.availableInBuffer(); + return currentLength - readPosition; } @Override public void close() { + if (closed) { + return; + } closed = true; + + // Return current buffer to pool + if (currentBuffer != null) { + bufferReturner.accept(currentBuffer); + currentBuffer = null; + } + + // Return any remaining batched buffers to pool + while (batchIndex < batchCount) { + bufferReturner.accept(localBatch[batchIndex].data()); + localBatch[batchIndex] = null; + batchIndex++; + } } @Override public long transferTo(OutputStream out) throws IOException { - if (closed) { + if (closed || eof) { return 0; } - // Borrow buffer from pool instead of allocating - byte[] buffer = exchange.borrowBuffer(TRANSFER_BUFFER_SIZE); - try { - long transferred = 0; - int read; - while ((read = exchange.readFromBuffer(buffer, 0, buffer.length)) >= 0) { - out.write(buffer, 0, read); - transferred += read; - } - return transferred; - } finally { - exchange.returnBuffer(buffer); + long transferred = 0; + + // First, transfer any remaining data in current buffer + if (currentBuffer != null && readPosition < currentLength) { + int remaining = currentLength - readPosition; + out.write(currentBuffer, readPosition, remaining); + transferred += remaining; + exchange.onDataConsumed(remaining); + readPosition = currentLength; + } + + // Pull and write chunks directly - no intermediate buffer, no double copy + while (pullNextChunk()) { + out.write(currentBuffer, 0, currentLength); + transferred += currentLength; + exchange.onDataConsumed(currentLength); + readPosition = currentLength; } + + return transferred; } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataOutputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataOutputStream.java index 70dedb19d..cb86f2992 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataOutputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataOutputStream.java @@ -10,16 +10,21 @@ /** * Output stream for writing request body as DATA frames. + * + *

      Uses pooled buffers from the muxer's ByteAllocator to reduce GC pressure. */ final class H2DataOutputStream extends OutputStream { private final H2Exchange exchange; - private final byte[] buffer; + private final H2Muxer muxer; + private byte[] buffer; private int pos = 0; private boolean closed = false; - H2DataOutputStream(H2Exchange exchange, int bufferSize) { + H2DataOutputStream(H2Exchange exchange, H2Muxer muxer, int bufferSize) { this.exchange = exchange; - this.buffer = new byte[Math.max(bufferSize, 1)]; + this.muxer = muxer; + // Borrow buffer from pool instead of allocating new + this.buffer = bufferSize > 0 ? muxer.borrowBuffer(bufferSize) : H2Constants.EMPTY_BYTES; } @Override @@ -35,8 +40,19 @@ public void write(int b) throws IOException { @Override public void write(byte[] b, int off, int len) throws IOException { - if (closed) + if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (closed) { throw new IOException("Stream closed"); + } + + // Fast path: large write - flush buffer if needed, then write directly + if (len >= buffer.length) { + flush(); + exchange.writeData(b, off, len, false); + return; + } + while (len > 0) { int space = buffer.length - pos; int toCopy = Math.min(space, len); @@ -65,12 +81,20 @@ public void close() throws IOException { } closed = true; - // Flush remaining data with END_STREAM - if (pos > 0) { - exchange.writeData(buffer, 0, pos, true); - pos = 0; - } else { - exchange.sendEndStream(); + try { + // Flush remaining data with END_STREAM + if (pos > 0) { + exchange.writeData(buffer, 0, pos, true); + pos = 0; + } else { + exchange.sendEndStream(); + } + } finally { + // Return buffer to pool + if (buffer.length > 0) { + muxer.returnBuffer(buffer); + buffer = null; + } } } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index d9d4b2b09..c4eb6a209 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -10,111 +10,95 @@ import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_PROTOCOL_ERROR; import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_STREAM_CLOSED; import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_END_STREAM; -import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_STATUS; +import static software.amazon.smithy.java.http.client.h2.H2StreamState.RS_DONE; +import static software.amazon.smithy.java.http.client.h2.H2StreamState.RS_ERROR; +import static software.amazon.smithy.java.http.client.h2.H2StreamState.RS_READING; +import static software.amazon.smithy.java.http.client.h2.H2StreamState.SS_CLOSED; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import java.net.SocketTimeoutException; +import java.util.ArrayDeque; import java.util.List; -import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.ReentrantLock; import software.amazon.smithy.java.http.api.HttpHeaders; import software.amazon.smithy.java.http.api.HttpRequest; import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.api.ModifiableHttpHeaders; import software.amazon.smithy.java.http.client.DelegatedClosingInputStream; import software.amazon.smithy.java.http.client.DelegatedClosingOutputStream; import software.amazon.smithy.java.http.client.HttpExchange; import software.amazon.smithy.java.http.client.h2.hpack.HeaderField; +import software.amazon.smithy.java.logging.InternalLogger; /** * HTTP/2 exchange implementation for a single stream with multiplexing support. * *

      This class manages the lifecycle of a single HTTP/2 stream (request/response pair). - * Response data is received from the connection's reader thread directly into a buffer, - * avoiding per-frame allocations. Headers and errors are signaled via condition variables. + * Response data is received from the connection's reader thread into a queue of data chunks. + * Headers and errors are signaled via condition variables. * *

      Stream Lifecycle

      *
        - *
      1. Constructor sends HEADERS frame
      2. + *
      3. Exchange created via {@link H2Muxer#newExchange}, HEADERS sent via {@link H2Muxer#submitHeaders}
      4. *
      5. {@link #requestBody()} returns output stream for DATA frames
      6. *
      7. {@link #responseHeaders()}/{@link #responseStatusCode()} read response HEADERS
      8. *
      9. {@link #responseBody()} returns input stream for response DATA frames
      10. *
      11. {@link #close()} sends RST_STREAM if needed and unregisters stream
      12. *
      * - *

      Zero-Allocation Read Path

      - *

      The reader thread writes DATA frame payloads directly into {@code dataBuffer}. - * The user thread reads directly from this buffer. Flow control ensures the buffer - * never overflows: we only send WINDOW_UPDATE after user reads consume data. + *

      Data Flow

      + *

      The reader thread enqueues DATA frame payloads via {@link #enqueueData}. The user + * thread drains chunks in batches via {@link #drainChunks} (used by H2DataInputStream). + * Pooled byte[] buffers are returned after consumption. Flow control sends WINDOW_UPDATE + * after data is consumed via {@link #onDataConsumed}. */ public final class H2Exchange implements HttpExchange { - /** - * Stream states per RFC 9113 Section 5.1. - */ - enum StreamState { - IDLE, // Initial state - OPEN, // After sending HEADERS without END_STREAM - HALF_CLOSED_LOCAL, // We sent END_STREAM, can still receive - HALF_CLOSED_REMOTE, // They sent END_STREAM, can still send - CLOSED // Both directions closed - } - - /** - * Read-side state machine for response processing. - */ - enum ReadState { - WAITING_HEADERS, // Initial state, waiting for response HEADERS - READING_DATA, // Got headers, now streaming DATA - DONE, // END_STREAM received - ERROR // Error occurred - } + private static final InternalLogger LOGGER = InternalLogger.getLogger(H2Exchange.class); - // Request pseudo-headers (only allowed in requests, not responses) - private static final Set REQUEST_PSEUDO_HEADERS = Set.of( - ":method", - ":scheme", - ":authority", - ":path"); + // VarHandle for atomic inWorkQueue CAS + private static final VarHandle IN_WORK_QUEUE_HANDLE; - // Shared empty array to avoid allocation - private static final byte[] EMPTY_DATA = new byte[0]; + static { + try { + IN_WORK_QUEUE_HANDLE = MethodHandles.lookup() + .findVarHandle(H2Exchange.class, "inWorkQueue", boolean.class); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } private final H2Muxer muxer; private final HttpRequest request; private volatile int streamId; + // Stream state machine (encapsulates packed bit-field with CAS operations) + private final H2StreamState state = new H2StreamState(); + // Pending headers from reader thread (protected by dataLock) private List pendingHeaders; private boolean pendingHeadersEndStream; - // === Data buffer for zero-allocation read path === - // Lazily allocated when first DATA frame arrives. Size = initial flow control window. - // Flow control ensures server can't send more than this before we send WINDOW_UPDATE. - private byte[] dataBuffer; + // === Data chunk queue === + // Queue of DataChunks received from reader thread. Each chunk contains one DATA frame payload. + // Flow control ensures total queued data never exceeds initial window size. + private final ArrayDeque dataQueue = new ArrayDeque<>(); - // Buffer positions - protected by dataLock - // writePos: where reader thread writes next (reader-thread owned, but read by user under lock) - // readPos: where user reads next (user-thread owned) - private int writePos = 0; - private int readPos = 0; - - // Read-side state machine and synchronization + // Read-side synchronization (state is in packedState) private final ReentrantLock dataLock = new ReentrantLock(); - private final Condition dataAvailable = dataLock.newCondition(); - private volatile ReadState readState = ReadState.WAITING_HEADERS; private volatile IOException readError; - // Stream state machine per RFC 9113 Section 5.1 - private volatile StreamState streamState = StreamState.IDLE; + // Lock-free signaling: waiting thread parks itself, producer unparks it without holding lock. + // This allows stream-switch signaling without double lock acquisition. + private volatile Thread waitingThread; // Stream-level timeouts (tick-based: 1 tick = TIMEOUT_POLL_INTERVAL_MS) private final long readTimeoutMs; @@ -123,13 +107,13 @@ enum ReadState { private final AtomicLong readSeq = new AtomicLong(); // Activity counter, incremented on read activity private volatile int readDeadlineTick; // 0 = no deadline, >0 = deadline tick private final AtomicBoolean readTimedOut = new AtomicBoolean(); // At-most-once timeout flag - private boolean waitingForData; // guarded by dataLock - true when VT is blocked waiting for data - // Response state - private volatile int statusCode = -1; + // Adaptive signaling: batch signals for fast transfers, but don't delay small/slow responses + // Signal when: queue was empty (first chunk), threshold reached, or endStream + private static final int SIGNAL_THRESHOLD = 4; // Signal when this many chunks are queued + + // Response headers (status code is in packedState) private volatile HttpHeaders responseHeaders; - private volatile boolean responseHeadersReceived = false; - private volatile boolean endStreamReceived = false; // Trailer headers per RFC 9113 Section 8.1 private volatile HttpHeaders trailerHeaders; @@ -138,8 +122,7 @@ enum ReadState { private long expectedContentLength = -1; // -1 means not specified private long receivedContentLength = 0; - // Request state - private volatile boolean endStreamSent = false; + // Request state (endStreamSent is in packedState) private volatile OutputStream requestOut; private volatile HttpHeaders requestTrailers; @@ -149,12 +132,13 @@ enum ReadState { // Close guard private final AtomicBoolean closed = new AtomicBoolean(false); - // Auto-close tracking: exchange closes when both streams are closed (count reaches 2) + // Auto-close tracking: exchange closes when both request and response streams are closed + private static final int BOTH_STREAMS_CLOSED = 2; // request stream + response stream private final AtomicInteger closedStreamCount = new AtomicInteger(0); - // Flow control - // sendWindow: Semaphore-based, VT blocks naturally when exhausted (no lock needed) - // streamRecvWindow: only accessed by application thread in readDataChunk() (single-threaded) + // === Flow control === + // sendWindow: monitor-based (synchronized + wait/notifyAll), VT blocks when exhausted + // streamRecvWindow: tracks receive window, accessed under dataLock private final FlowControlWindow sendWindow; private final int initialWindowSize; private int streamRecvWindow; @@ -166,6 +150,12 @@ enum ReadState { // Flag to prevent duplicate additions to connection's work queue volatile boolean inWorkQueue; + // === WRITE COMPLETION SIGNALING === + // Lock-free signaling using LockSupport to avoid monitor inflation overhead + private volatile Thread waitingWriter; + private volatile boolean writeCompleted; + private volatile Throwable writeError; + /** * Create a new HTTP/2 exchange without a stream ID. * @@ -195,7 +185,7 @@ enum ReadState { } /** - * Set the stream ID. Called by connection when allocating under lock. + * Set the stream ID. Called by muxer when allocating stream ID. */ void setStreamId(int streamId) { this.streamId = streamId; @@ -260,48 +250,99 @@ private void clearReadDeadline() { } /** - * Get the muxer for this exchange. + * Called when headers are encoded and about to be sent. + * Atomically transitions stream state and optionally marks end stream sent. */ - H2Muxer getMuxer() { - return muxer; + void onHeadersEncoded(boolean endStream) { + state.onHeadersEncoded(endStream); + } + + // ==================== WRITE COMPLETION SIGNALING ==================== + + /** + * Block until signaled by the writer thread, then check for errors. + * + *

      Called by the VT that owns this exchange after submitting work to the muxer. + * Uses lock-free LockSupport signaling to avoid monitor inflation overhead. + * + * @throws IOException if a write error occurred + */ + void awaitWriteCompletion() throws IOException { + // Fast path: already completed + if (writeCompleted) { + writeCompleted = false; + checkWriteError(); + return; + } + + // Register as waiting and park until signaled + waitingWriter = Thread.currentThread(); + try { + while (!writeCompleted) { + LockSupport.park(); + if (Thread.interrupted()) { + throw new IOException("Interrupted waiting for write completion"); + } + } + } finally { + waitingWriter = null; + } + + writeCompleted = false; + checkWriteError(); + } + + private void checkWriteError() throws IOException { + Throwable error = writeError; + if (error != null) { + writeError = null; + if (error instanceof IOException ioe) { + throw ioe; + } + throw new IOException("Write failed", error); + } } /** - * Borrow a buffer from the muxer's pool. + * Signal the waiting writer that the write completed successfully. * - * @param minSize minimum size needed - * @return a buffer of at least minSize bytes + *

      Called by the muxer worker thread after completing a write. + * Lock-free: uses volatile write + LockSupport.unpark(). */ - byte[] borrowBuffer(int minSize) { - return muxer.borrowBuffer(minSize); + void signalWriteSuccess() { + writeCompleted = true; + Thread t = waitingWriter; + if (t != null) { + LockSupport.unpark(t); + } } /** - * Return a buffer to the muxer's pool. + * Signal the waiting writer that the write failed. * - *

      Called by the writer thread after consuming a PendingWrite. + *

      Called by the muxer worker thread when a write error occurs. + * Lock-free: uses volatile writes + LockSupport.unpark(). * - * @param buffer the buffer to return + * @param error the error that occurred */ - void returnBuffer(byte[] buffer) { - muxer.returnBuffer(buffer); + void signalWriteFailure(Throwable error) { + writeError = error; + writeCompleted = true; + Thread t = waitingWriter; + if (t != null) { + LockSupport.unpark(t); + } } /** - * Called by connection after headers are encoded but before they're written. + * Return a buffer to the muxer's pool. * - *

      Updates the exchange state to reflect that headers have been queued for sending. - * The actual I/O happens outside the HPACK lock for better concurrency. + *

      Called by the writer thread after consuming a PendingWrite. * - * @param endStream true if the HEADERS frame will have END_STREAM flag + * @param buffer the buffer to return */ - void onHeadersEncoded(boolean endStream) { - if (endStream) { - endStreamSent = true; - streamState = StreamState.HALF_CLOSED_LOCAL; - } else { - streamState = StreamState.OPEN; - } + void returnBuffer(byte[] buffer) { + muxer.returnBuffer(buffer); } /** @@ -318,12 +359,14 @@ void deliverHeaders(List fields, boolean endStream) { try { pendingHeaders = fields; pendingHeadersEndStream = endStream; - if (waitingForData) { - dataAvailable.signalAll(); - } } finally { dataLock.unlock(); } + // Signal outside lock - lock-free wakeup + Thread t = waitingThread; + if (t != null) { + LockSupport.unpark(t); + } } /** @@ -332,14 +375,13 @@ void deliverHeaders(List fields, boolean endStream) { *

      Signals the user thread that the connection has closed with an error. */ void signalConnectionClosed(Throwable error) { - dataLock.lock(); - try { - this.endStreamReceived = true; - this.readError = (error instanceof IOException ioe) ? ioe : new IOException("Connection closed", error); - this.readState = ReadState.ERROR; - dataAvailable.signalAll(); - } finally { - dataLock.unlock(); + // Set error state without updating stream state (unlike normal end-stream) + state.setErrorState(); + this.readError = (error instanceof IOException ioe) ? ioe : new IOException("Connection closed", error); + // Signal outside lock - lock-free wakeup + Thread t = waitingThread; + if (t != null) { + LockSupport.unpark(t); } } @@ -350,138 +392,201 @@ void signalConnectionClosed(Throwable error) { * instead of timing out. */ void signalStreamError(H2Exception error) { - dataLock.lock(); - try { - this.endStreamReceived = true; - this.readError = new IOException("Stream error", error); - this.readState = ReadState.ERROR; - dataAvailable.signalAll(); - } finally { - dataLock.unlock(); + // Set error state without updating stream state (unlike normal end-stream) + state.setErrorState(); + this.readError = new IOException("Stream error", error); + // Signal outside lock - lock-free wakeup + Thread t = waitingThread; + if (t != null) { + LockSupport.unpark(t); } } /** - * Get the data buffer for direct writes by the reader thread. + * Enqueue a data chunk from the reader thread. * - *

      Must be called while holding dataLock (via ensureBufferSpace). - */ - byte[] getDataBuffer() { - return dataBuffer; - } - - /** - * Get the current write position for direct writes by the reader thread. + *

      This method is called by the reader thread to add a byte[] containing + * DATA frame payload to the queue. * - *

      Must be called while holding dataLock (via ensureBufferSpace). - */ - int getWritePos() { - return writePos; - } - - /** - * Called by reader thread after writing data to advance the write position. - * - *

      Note: We do NOT call updateStreamStateOnEndStream() here. The stream - * state transition happens when the user finishes reading (returns -1), - * not when data arrives. This avoids race conditions where the stream - * transitions to CLOSED before the user processes pending headers. - * - * @param bytesWritten number of bytes written + * @param data the byte array containing data, or null for end-stream-only signal + * @param length the number of valid bytes in data * @param endStream whether END_STREAM flag was set + * @param moreDataBuffered true if more data is already buffered in the socket read buffer, + * used to defer signaling when processing a burst of frames */ - void commitWrite(int bytesWritten, boolean endStream) { + void enqueueData(byte[] data, int length, boolean endStream, boolean moreDataBuffered) { + boolean shouldSignal; dataLock.lock(); try { - boolean wasEmpty = (writePos == readPos); - - writePos += bytesWritten; - if (bytesWritten > 0) { - onReadActivity(); // Extend timeout when data arrives + int queueSizeBefore = dataQueue.size(); + + if (data != null && length > 0) { + dataQueue.add(new DataChunk(data, length, endStream)); + // Note: onReadActivity moved to drainChunks (consumer side) for batching + } else if (data != null) { + // Empty buffer - return to pool immediately + muxer.returnBuffer(data); } + if (endStream) { - this.endStreamReceived = true; - this.readState = ReadState.DONE; + state.setEndStreamReceivedFlag(); // Just set flag + readState, don't update stream state clearReadDeadline(); // No more data expected, clear timeout } - // Only wake the reader if it's actually blocked waiting - if ((wasEmpty || endStream) && waitingForData) { - dataAvailable.signalAll(); - } + // Pipelined adaptive signaling: balance batching with parallelism. + // With lock-free signaling, wakeup cost is low enough to prioritize parallelism. + // Signal if: + // 1. endStream - response complete + // 2. threshold reached - safety valve for huge buffers + // 3. queueWasEmpty - TTFB optimization, enables consumer to process in parallel + // 4. buffer empty - we've consumed everything the kernel gave us + int queueSize = dataQueue.size(); + boolean queueWasEmpty = queueSizeBefore == 0 && queueSize > 0; + boolean thresholdReached = queueSize >= SIGNAL_THRESHOLD; + + shouldSignal = endStream + || thresholdReached + || queueWasEmpty + || !moreDataBuffered; } finally { dataLock.unlock(); } + + // Signal outside lock - lock-free wakeup + if (shouldSignal) { + Thread t = waitingThread; + if (t != null) { + LockSupport.unpark(t); + } + } } - // Initial buffer size - small to avoid waste on tiny responses - private static final int INITIAL_BUFFER_SIZE = 1024; + /** + * Signal the consumer that data is available. + * + *

      Called by H2Connection only when switching from this stream to a different + * stream (to flush pending data before processing another stream's frames). + * This is lock-free and can be called without holding any locks. + */ + void signalDataAvailable() { + Thread t = waitingThread; + if (t != null) { + LockSupport.unpark(t); + } + } /** - * Called by reader thread to ensure buffer has space, allocating or compacting as needed. + * Drain multiple data chunks from the queue into a destination deque. * - *

      Buffer is borrowed from the connection's pool when first needed, and returned - * when the exchange is closed. This reduces GC pressure for repeated requests. + *

      This method is used by H2DataInputStream for batch dequeuing to reduce + * lock contention. Instead of acquiring the lock once per chunk, the consumer + * can pull multiple chunks in a single lock acquisition. * - * @param requiredSpace the amount of space needed - * @return true if space is available, false if buffer is genuinely full + * @param dest the destination array to drain chunks into + * @param maxChunks maximum number of chunks to drain + * @return number of chunks drained, or -1 if EOF + * @throws IOException if an error occurs or the stream is in error state */ - boolean ensureBufferSpace(int requiredSpace) { + int drainChunks(DataChunk[] dest, int maxChunks) throws IOException { + // If we haven't received headers yet, read them first + if (!state.isResponseHeadersReceived()) { + readResponseHeaders(); + } + dataLock.lock(); try { - // Lazy allocation on first DATA frame - borrow from connection pool - if (dataBuffer == null) { - int size = Math.max(INITIAL_BUFFER_SIZE, requiredSpace); - size = Math.min(size, initialWindowSize); - dataBuffer = muxer.borrowBuffer(size); - return true; - } + // Wait for data, EOF, or error + while (dataQueue.isEmpty() && state.getReadState() == RS_READING) { + // Check for pending trailers + if (pendingHeaders != null) { + List fields = pendingHeaders; + boolean endStream = pendingHeadersEndStream; + pendingHeaders = null; + handleHeadersEvent(fields, endStream); + if (state.getReadState() == RS_DONE) { + break; + } + } - if (writePos + requiredSpace <= dataBuffer.length) { - // Already have space - return true; + // Wait for data to arrive using lock-free signaling + // 1. Register ourselves as the waiting thread + // 2. Release lock (so producer can add data) + // 3. Park (will be unparked by producer) + // 4. Reacquire lock and clear waiting thread + waitingThread = Thread.currentThread(); + dataLock.unlock(); + try { + LockSupport.park(); + if (Thread.interrupted()) { + throw new IOException("Interrupted waiting for data"); + } + } finally { + dataLock.lock(); + waitingThread = null; + } } - // Try compaction first - if (readPos > 0) { - int remaining = writePos - readPos; - if (remaining > 0) { - System.arraycopy(dataBuffer, readPos, dataBuffer, 0, remaining); - } - writePos = remaining; - readPos = 0; + // Check for error + if (state.getReadState() == RS_ERROR) { + throw readError; + } - if (writePos + requiredSpace <= dataBuffer.length) { - return true; + // Check for EOF (no more data and stream is done) + if (dataQueue.isEmpty() && state.getReadState() == RS_DONE) { + // Auto-close stream when user reads to EOF to prevent resource leaks + // even if they forget to call close() explicitly + if (state.getStreamState() != SS_CLOSED) { + state.setStreamStateClosed(); + if (streamId > 0) { + muxer.releaseStream(streamId); + } } + validateContentLength(); + return -1; // EOF } - // Need to grow buffer - get a larger one from pool. Grow by 4x to reduce resizing frequency. - int newSize = dataBuffer.length * 4; - while (newSize < writePos + requiredSpace) { - newSize *= 2; + // Drain up to maxChunks from queue + int drained = 0; + while (drained < maxChunks && !dataQueue.isEmpty()) { + dest[drained++] = dataQueue.poll(); } - newSize = Math.min(newSize, initialWindowSize); - if (writePos + requiredSpace > newSize) { - return false; + // Update timeout once per batch (moved from enqueueData for efficiency) + if (drained > 0) { + onReadActivity(); } - byte[] oldBuffer = dataBuffer; - dataBuffer = muxer.borrowBuffer(newSize); - if (writePos > 0) { - System.arraycopy(oldBuffer, 0, dataBuffer, 0, writePos); - } - // Return old buffer to pool - muxer.returnBuffer(oldBuffer); - return true; + return drained; } finally { dataLock.unlock(); } } /** - * Called by connection when SETTINGS changes initial window size. + * Called by H2DataInputStream when data is consumed. + * + *

      Updates content length tracking and flow control. Sends WINDOW_UPDATE + * when the receive window drops below the threshold. + * + * @param bytesConsumed number of bytes consumed + */ + void onDataConsumed(int bytesConsumed) { + receivedContentLength += bytesConsumed; + + // Update flow control if stream is still open + if (state.getReadState() != RS_DONE) { + try { + updateStreamRecvWindow(bytesConsumed); + } catch (IOException e) { + // Flow control update failed - best effort, log and continue. + // The stream will eventually fail on timeout if this is persistent. + LOGGER.debug("Failed to send WINDOW_UPDATE for stream {}: {}", streamId, e.getMessage()); + } + } + } + + /** + * Called by muxer when SETTINGS changes initial window size. */ void adjustSendWindow(int delta) { sendWindow.adjust(delta); @@ -493,12 +598,12 @@ public HttpRequest request() { } @Override - public OutputStream requestBody() { + public synchronized OutputStream requestBody() { if (requestOut == null) { // If no request body is expected, then return a no-op stream. - H2DataOutputStream rawOut = endStreamSent - ? new H2DataOutputStream(this, 0) - : new H2DataOutputStream(this, muxer.getRemoteMaxFrameSize()); + H2DataOutputStream rawOut = state.isEndStreamSent() + ? new H2DataOutputStream(this, muxer, 0) + : new H2DataOutputStream(this, muxer, muxer.getRemoteMaxFrameSize()); requestOut = new DelegatedClosingOutputStream(rawOut, rw -> { rw.close(); // Send END_STREAM onRequestStreamClosed(); @@ -508,33 +613,44 @@ public OutputStream requestBody() { } @Override - public InputStream responseBody() throws IOException { + public synchronized InputStream responseBody() throws IOException { // Ensure we have response headers first - if (!responseHeadersReceived) { + if (!state.isResponseHeadersReceived()) { readResponseHeaders(); } if (responseIn == null) { - responseIn = new DelegatedClosingInputStream(new H2DataInputStream(this), this::onResponseStreamClosed); + // Optimization: for empty responses, return a null stream to avoid H2DataInputStream allocation. + // But only do this if: + // - content-length is explicitly 0, OR + // - end stream is received AND no data is queued (truly empty response) + boolean isEmpty = expectedContentLength == 0 || (state.isEndStreamReceived() && dataQueue.isEmpty()); + if (isEmpty) { + var nio = InputStream.nullInputStream(); + responseIn = new DelegatedClosingInputStream(nio, this::onResponseStreamClosed); + } else { + H2DataInputStream dataStream = new H2DataInputStream(this, muxer::returnBuffer); + responseIn = new DelegatedClosingInputStream(dataStream, this::onResponseStreamClosed); + } } return responseIn; } private void onRequestStreamClosed() throws IOException { - if (closedStreamCount.incrementAndGet() == 2) { + if (closedStreamCount.incrementAndGet() == BOTH_STREAMS_CLOSED) { close(); } } private void onResponseStreamClosed(InputStream _ignored) throws IOException { - if (closedStreamCount.incrementAndGet() == 2) { + if (closedStreamCount.incrementAndGet() == BOTH_STREAMS_CLOSED) { close(); } } @Override public HttpHeaders responseHeaders() throws IOException { - if (!responseHeadersReceived) { + if (!state.isResponseHeadersReceived()) { readResponseHeaders(); } return responseHeaders; @@ -542,10 +658,10 @@ public HttpHeaders responseHeaders() throws IOException { @Override public int responseStatusCode() throws IOException { - if (!responseHeadersReceived) { + if (!state.isResponseHeadersReceived()) { readResponseHeaders(); } - return statusCode; + return state.getStatusCode(); } @Override @@ -570,43 +686,38 @@ public void close() { } // Close request output if not already closed - if (requestOut != null && !endStreamSent) { + if (requestOut != null && !state.isEndStreamSent()) { try { requestOut.close(); } catch (IOException ignored) {} } // If response not fully received and stream was started, queue RST_STREAM - if (!endStreamReceived && streamId > 0 && streamState != StreamState.CLOSED) { - try { - // Short timeout for cleanup - muxer.queueControlFrame(streamId, H2Muxer.ControlFrameType.RST_STREAM, ERROR_CANCEL, 100); - } catch (IOException ignored) { - // Best-effort cleanup. If queue is full, stream is closing anyway. - } + if (!state.isEndStreamReceived() && streamId > 0 && state.getStreamState() != SS_CLOSED) { + // Best-effort cleanup - CLQ never blocks or fails + muxer.queueControlFrame(streamId, H2Muxer.ControlFrameType.RST_STREAM, ERROR_CANCEL, 100); // Signal end to any waiting consumers - dataLock.lock(); - try { - readState = ReadState.DONE; - dataAvailable.signalAll(); - } finally { - dataLock.unlock(); + state.setReadStateDone(); + // Signal outside lock - lock-free wakeup + Thread t = waitingThread; + if (t != null) { + LockSupport.unpark(t); } } - // Return buffer to connection pool for reuse + // Return all queued buffers to connection pool for reuse dataLock.lock(); try { - if (dataBuffer != null) { - muxer.returnBuffer(dataBuffer); - dataBuffer = null; + DataChunk chunk; + while ((chunk = dataQueue.poll()) != null) { + muxer.returnBuffer(chunk.data()); } } finally { dataLock.unlock(); } // Mark stream as closed - streamState = StreamState.CLOSED; + state.setStreamStateClosed(); // Unregister from connection (only if stream was registered) if (streamId > 0) { @@ -627,22 +738,26 @@ private void awaitEvent() throws IOException { dataLock.lock(); try { // Wait for headers, error, or data (which also signals) - while (pendingHeaders == null && readState != ReadState.ERROR && readState != ReadState.DONE) { - waitingForData = true; + int rs; + while (pendingHeaders == null && (rs = state.getReadState()) != RS_ERROR && rs != RS_DONE) { + // Wait using lock-free signaling + waitingThread = Thread.currentThread(); + dataLock.unlock(); try { - dataAvailable.await(); // Untimed: muxer watchdog handles timeout + LockSupport.park(); // Untimed: muxer watchdog handles timeout + if (Thread.interrupted()) { + throw new IOException("Interrupted waiting for response"); + } } finally { - waitingForData = false; + dataLock.lock(); + waitingThread = null; } } // Check for error - if (readState == ReadState.ERROR) { + if (state.getReadState() == RS_ERROR) { throw readError; } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IOException("Interrupted waiting for response", e); } finally { dataLock.unlock(); } @@ -657,7 +772,7 @@ private void awaitEvent() throws IOException { private void readResponseHeaders() throws IOException { onReadActivity(); // Start timeout when beginning to read response - while (!responseHeadersReceived) { + while (!state.isResponseHeadersReceived()) { awaitEvent(); dataLock.lock(); @@ -669,7 +784,7 @@ private void readResponseHeaders() throws IOException { // Process headers (can throw) handleHeadersEvent(fields, endStream); - } else if (readState == ReadState.DONE) { + } else if (state.getReadState() == RS_DONE) { throw new IOException("Stream ended before response headers received"); } } finally { @@ -685,12 +800,20 @@ private void readResponseHeaders() throws IOException { * @param isEndStream whether END_STREAM flag was set */ private void handleHeadersEvent(List fields, boolean isEndStream) throws IOException { + int ss = state.getStreamState(); + + // Allow processing headers if the stream is CLOSED but closed cleanly (RS_DONE) + // and we haven't processed the initial headers yet. + // This handles the race where Reader processes HEADERS -> DATA+ES before App processes HEADERS. + boolean cleanCloseRace = (ss == SS_CLOSED && state.getReadState() == RS_DONE + && !state.isResponseHeadersReceived()); + // Validate stream state per RFC 9113 Section 5.1 - if (streamState == StreamState.CLOSED) { + if (ss == SS_CLOSED && !cleanCloseRace) { throw new H2Exception(ERROR_STREAM_CLOSED, streamId, "Received HEADERS on closed stream"); } - if (!responseHeadersReceived) { + if (!state.isResponseHeadersReceived()) { // This is either informational (1xx) or final response headers if (fields.isEmpty()) { throw new IOException("Empty HEADERS frame received"); @@ -708,27 +831,11 @@ private void handleHeadersEvent(List fields, boolean isEndStream) t } if (isEndStream) { - endStreamReceived = true; - readState = ReadState.DONE; + state.markEndStreamReceived(); // Atomically sets flag, read state to DONE, updates stream state clearReadDeadline(); // No more data expected - updateStreamStateOnEndStream(); validateContentLength(); - } else if (responseHeadersReceived && readState != ReadState.DONE) { - // Got final headers, transition to READING_DATA - // (but don't overwrite DONE if DATA+END_STREAM already arrived) - readState = ReadState.READING_DATA; - } - } - - /** - * Update stream state when END_STREAM is received. - */ - private void updateStreamStateOnEndStream() { - if (streamState == StreamState.OPEN) { - streamState = StreamState.HALF_CLOSED_REMOTE; - } else if (streamState == StreamState.HALF_CLOSED_LOCAL) { - streamState = StreamState.CLOSED; } + // Note: setResponseHeadersReceived already transitions WAITING->READING } /** @@ -740,93 +847,18 @@ private void updateStreamStateOnEndStream() { * @param isEndStream whether this HEADERS frame has END_STREAM flag */ private void processResponseHeaders(List fields, boolean isEndStream) throws IOException { - ModifiableHttpHeaders headers = HttpHeaders.ofModifiable(); - int parsedStatusCode = -1; - boolean seenRegularHeader = false; - long contentLength = -1; - - for (HeaderField field : fields) { - String name = field.name(); - String value = field.value(); - - if (name.startsWith(":")) { - // Pseudo-header validation per RFC 9113 Section 8.3 - // RFC 9113 Section 8.3: All pseudo-headers MUST appear before regular headers - if (seenRegularHeader) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, - streamId, - "Pseudo-header '" + name + "' appears after regular header (RFC 9113 Section 8.3)"); - } - - if (name.equals(PSEUDO_STATUS)) { - // RFC 9113 Section 8.3.2: Response MUST have exactly one :status - if (parsedStatusCode != -1) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, - streamId, - "Multiple :status pseudo-headers in response"); - } - try { - parsedStatusCode = Integer.parseInt(value); - } catch (NumberFormatException e) { - throw new IOException("Invalid :status value: " + value); - } - } else if (REQUEST_PSEUDO_HEADERS.contains(name)) { - // RFC 9113 Section 8.3: Request pseudo-headers are NOT allowed in responses - throw new H2Exception(ERROR_PROTOCOL_ERROR, - streamId, - "Request pseudo-header '" + name + "' not allowed in response (RFC 9113 Section 8.3)"); - } else { - // Unknown pseudo-header - RFC 9113 says endpoints MUST treat as malformed - throw new H2Exception(ERROR_PROTOCOL_ERROR, - streamId, - "Unknown pseudo-header '" + name + "' in response"); - } - } else { - // Regular header - seenRegularHeader = true; - - // Track Content-Length for validation per RFC 9113 Section 8.1.1 - if ("content-length".equals(name)) { - try { - long parsedLength = Long.parseLong(value); - if (contentLength != -1 && contentLength != parsedLength) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, - streamId, - "Multiple different Content-Length values"); - } - contentLength = parsedLength; - } catch (NumberFormatException e) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, - streamId, - "Invalid Content-Length value: " + value); - } - } - - headers.addHeader(name, value); - } - } - - if (parsedStatusCode == -1) { - throw new IOException("Response missing :status pseudo-header"); - } + H2ResponseHeaderProcessor.Result result = + H2ResponseHeaderProcessor.processResponseHeaders(fields, streamId, isEndStream); - // Check if this is an informational (1xx) response - skip and wait for final response - if (parsedStatusCode >= 100 && parsedStatusCode < 200) { - // RFC 9113 Section 8.1.1: 1xx responses MUST NOT have END_STREAM - if (isEndStream) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, - streamId, - "Informational response (1xx) must not have END_STREAM"); - } - // Don't store 1xx responses - just wait for final response + if (result.isInformational()) { + // 1xx response - wait for final response return; } // This is the final response (2xx-5xx) - this.statusCode = parsedStatusCode; - this.responseHeaders = headers; - this.expectedContentLength = contentLength; - this.responseHeadersReceived = true; + this.responseHeaders = result.headers(); + this.expectedContentLength = result.contentLength(); + state.setResponseHeadersReceived(result.statusCode()); } /** @@ -840,19 +872,7 @@ private void processResponseHeaders(List fields, boolean isEndStrea * @param fields the pre-decoded header fields */ private void processTrailers(List fields) throws IOException { - ModifiableHttpHeaders trailers = HttpHeaders.ofModifiable(); - for (HeaderField field : fields) { - String name = field.name(); - // RFC 9113 Section 8.1: Trailers MUST NOT contain pseudo-headers - if (name.startsWith(":")) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, - streamId, - "Trailer contains pseudo-header '" + name + "' (RFC 9113 Section 8.1)"); - } - trailers.addHeader(name, field.value()); - } - - this.trailerHeaders = trailers; + this.trailerHeaders = H2ResponseHeaderProcessor.processTrailers(fields, streamId); } /** @@ -860,27 +880,21 @@ private void processTrailers(List fields) throws IOException { * RFC 9113 Section 8.1.1. */ private void validateContentLength() throws IOException { - if (expectedContentLength >= 0 && receivedContentLength != expectedContentLength) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, - streamId, - "Content-Length mismatch: expected " + expectedContentLength + - " bytes, received " + receivedContentLength + " bytes (RFC 9113 Section 8.1.1)"); - } + H2ResponseHeaderProcessor.validateContentLength(expectedContentLength, receivedContentLength, streamId); } /** * Update stream send window from WINDOW_UPDATE frame. * *

      Called by the connection's reader thread when a stream-level - * WINDOW_UPDATE is received. This releases permits to the FlowControlWindow, - * which will automatically wake any blocked threads. + * WINDOW_UPDATE is received. This releases capacity to the FlowControlWindow + * and wakes any blocked threads via notifyAll(). * * @param increment the window size increment * @throws H2Exception if the increment causes overflow */ void updateStreamSendWindow(int increment) throws H2Exception { // Check for overflow per RFC 9113 before releasing - // Note: with Semaphore, we can't easily detect overflow, so we check available + increment int currentWindow = sendWindow.available(); if ((long) currentWindow + increment > Integer.MAX_VALUE) { throw new H2Exception(ERROR_FLOW_CONTROL_ERROR, @@ -895,15 +909,12 @@ void updateStreamSendWindow(int increment) throws H2Exception { * *

      Uses the SPSC (single-producer, single-consumer) pattern: *

        - *
      1. VT acquires flow control (blocks naturally via Semaphore)
      2. + *
      3. VT acquires flow control (blocks naturally via FlowControlWindow monitor)
      4. *
      5. VT copies data to pooled buffer and adds to pendingWrites queue
      6. - *
      7. VT signals writer thread via dataWorkQueue
      8. + *
      9. VT signals writer thread via muxer
      10. *
      11. Writer thread drains pendingWrites and writes frames
      12. *
      * - *

      This eliminates contention on the shared ArrayBlockingQueue by using - * per-exchange queues for data. - * * @throws SocketTimeoutException if write timeout expires waiting for flow control window */ void writeData(byte[] data, int offset, int length, boolean endStream) throws IOException { @@ -916,9 +927,9 @@ void writeData(byte[] data, int offset, int length, boolean endStream) throws IO int maxFrameSize = muxer.getRemoteMaxFrameSize(); int toSend = Math.min(length, maxFrameSize); - // Acquire stream-level flow control (blocks naturally via Semaphore) + // Acquire stream-level flow control (uses tick-based timeout) try { - if (!sendWindow.tryAcquire(toSend, writeTimeoutMs, TimeUnit.MILLISECONDS)) { + if (!sendWindow.tryAcquire(toSend, writeTimeoutMs)) { throw new SocketTimeoutException( "Write timed out after " + writeTimeoutMs + "ms waiting for stream flow control window"); } @@ -951,12 +962,11 @@ void writeData(byte[] data, int offset, int length, boolean endStream) throws IO // Add to pendingWrites queue (lock-free concurrent queue) PendingWrite pw = new PendingWrite(); - pw.init(buf, 0, toSend, isLastChunk && endStream && !hasTrailers, flags); + pw.init(buf, 0, toSend, flags); pendingWrites.add(pw); - // Signal writer thread if not already in work queue - if (!inWorkQueue) { - inWorkQueue = true; + // Signal writer thread if not already in work queue (atomic CAS to avoid races) + if (IN_WORK_QUEUE_HANDLE.compareAndSet(this, false, true)) { muxer.signalDataReady(this); } @@ -969,128 +979,32 @@ void writeData(byte[] data, int offset, int length, boolean endStream) throws IO // Send trailers with END_STREAM muxer.queueTrailers(streamId, requestTrailers); } - endStreamSent = true; - // Update stream state - if (streamState == StreamState.OPEN) { - streamState = StreamState.HALF_CLOSED_LOCAL; - } else if (streamState == StreamState.HALF_CLOSED_REMOTE) { - streamState = StreamState.CLOSED; - } + state.markEndStreamSent(); // Atomically sets flag and updates stream state } } /** * Send END_STREAM without data, or send trailers if set. + * + *

      Uses the same pendingWrites queue as writeData() to ensure proper ordering. + * This prevents END_STREAM from being sent before pending DATA frames. */ void sendEndStream() throws IOException { - if (!endStreamSent) { + if (!state.isEndStreamSent()) { if (requestTrailers != null) { muxer.queueTrailers(streamId, requestTrailers); } else { - muxer.queueData(streamId, EMPTY_DATA, 0, 0, FLAG_END_STREAM); - } - endStreamSent = true; - // Update stream state - if (streamState == StreamState.OPEN) { - streamState = StreamState.HALF_CLOSED_LOCAL; - } else if (streamState == StreamState.HALF_CLOSED_REMOTE) { - streamState = StreamState.CLOSED; - } - } - } - - /** - * Read data from the response buffer. - * - *

      This is the primary read method for H2DataInputStream. Data is read - * directly from the exchange's buffer, which is filled by the reader thread. - * - * @param buf the destination buffer - * @param off offset in the destination buffer - * @param len maximum number of bytes to read - * @return number of bytes read, or -1 if end of stream - * @throws IOException if an error occurs - */ - int readFromBuffer(byte[] buf, int off, int len) throws IOException { - // If we haven't received headers yet, read them first - if (!responseHeadersReceived) { - readResponseHeaders(); - } - - int bytesRead; - dataLock.lock(); - try { - // Wait for data, EOF, or error - while (readPos == writePos && readState == ReadState.READING_DATA) { - // Check for pending trailers - if (pendingHeaders != null) { - List fields = pendingHeaders; - boolean endStream = pendingHeadersEndStream; - pendingHeaders = null; - handleHeadersEvent(fields, endStream); - if (readState == ReadState.DONE) { - break; - } - } - - // Wait for data to arrive - waitingForData = true; - try { - dataAvailable.await(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IOException("Interrupted waiting for data", e); - } finally { - waitingForData = false; + // Use pendingWrites queue (same as writeData) to ensure ordering + PendingWrite pw = new PendingWrite(); + pw.init(H2Constants.EMPTY_BYTES, 0, 0, FLAG_END_STREAM); + pendingWrites.add(pw); + + // Signal writer thread + if (IN_WORK_QUEUE_HANDLE.compareAndSet(this, false, true)) { + muxer.signalDataReady(this); } } - - // Check for error - if (readState == ReadState.ERROR) { - throw readError; - } - - // Check for EOF (no more data and stream is done) - if (readPos == writePos && readState == ReadState.DONE) { - updateStreamStateOnEndStream(); - validateContentLength(); - return -1; - } - - // Copy from buffer to user's array - int available = writePos - readPos; - int toCopy = Math.min(available, len); - System.arraycopy(dataBuffer, readPos, buf, off, toCopy); - readPos += toCopy; - bytesRead = toCopy; - - // Track received content length for validation - receivedContentLength += toCopy; - - } finally { - dataLock.unlock(); - } - - // Update stream-level flow control outside the lock - // This sends WINDOW_UPDATE after user reads consume data - if (bytesRead > 0 && readState != ReadState.DONE) { - updateStreamRecvWindow(bytesRead); - } - - return bytesRead; - } - - /** - * Get available bytes in buffer without blocking. - * - * @return number of bytes available for immediate read - */ - int availableInBuffer() { - dataLock.lock(); - try { - return dataBuffer == null ? 0 : writePos - readPos; - } finally { - dataLock.unlock(); + state.markEndStreamSent(); // Atomically sets flag and updates stream state } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java index 28be8b3ef..f89e426f7 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java @@ -5,6 +5,7 @@ package software.amazon.smithy.java.http.client.h2; +import static software.amazon.smithy.java.http.client.h2.H2Constants.CONNECTION_PREFACE; import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_FRAME_SIZE_ERROR; import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_PROTOCOL_ERROR; import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_ACK; @@ -25,11 +26,10 @@ import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_WINDOW_UPDATE; import static software.amazon.smithy.java.http.client.h2.H2Constants.frameTypeName; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; +import software.amazon.smithy.java.http.client.UnsyncBufferedInputStream; +import software.amazon.smithy.java.http.client.UnsyncBufferedOutputStream; +import software.amazon.smithy.java.io.ByteBufferOutputStream; /** * HTTP/2 frame encoding and decoding. @@ -49,119 +49,337 @@ */ final class H2FrameCodec { - private final InputStream in; - private final OutputStream out; + private final UnsyncBufferedInputStream in; + private final UnsyncBufferedOutputStream out; private final int maxFrameSize; - // Separate buffers for reading and writing to avoid concurrent access issues. - // The reader thread uses readHeaderBuf, while writer threads use writeHeaderBuf. - private final byte[] readHeaderBuf = new byte[FRAME_HEADER_SIZE]; + // Write header buffer - used by writer thread only. + // Read operations use zero-copy direct buffer access from UnsyncBufferedInputStream. private final byte[] writeHeaderBuf = new byte[FRAME_HEADER_SIZE]; - // Scratch buffer for control frames to avoid allocation on hot path. - // Covers PING(8), SETTINGS(up to ~10 params = 60), WINDOW_UPDATE(4), RST_STREAM(4), - // PRIORITY(5), and small GOAWAY frames. Larger payloads fall back to allocation. - private static final int CONTROL_FRAME_SCRATCH_SIZE = 64; - private final byte[] controlFrameScratch = new byte[CONTROL_FRAME_SCRATCH_SIZE]; + // Scratch buffer for writing control frames - writer thread only. + // Used for WINDOW_UPDATE(4), RST_STREAM(4), and GOAWAY(8+) payloads. + // Read operations use zero-copy direct buffer access from UnsyncBufferedInputStream. + private static final int WRITE_SCRATCH_SIZE = 64; + private final byte[] writeScratch = new byte[WRITE_SCRATCH_SIZE]; - // Shared empty array for zero-length payloads - private static final byte[] EMPTY_PAYLOAD = new byte[0]; + // Reusable buffer for accumulating header blocks when CONTINUATION frames are needed. + // Reader thread only. Reset and reused across requests to avoid repeated allocations. + private final ByteBufferOutputStream headerBlockBuffer = new ByteBufferOutputStream(4096); - H2FrameCodec(InputStream in, OutputStream out, int maxFrameSize) { + // Current frame state (filled by nextFrame()) - stateful parser pattern + private int currentType; + private int currentFlags; + private int currentStreamId; + private int currentPayloadLength; + + H2FrameCodec(UnsyncBufferedInputStream in, UnsyncBufferedOutputStream out, int maxFrameSize) { this.in = in; this.out = out; this.maxFrameSize = maxFrameSize; } /** - * Read a frame from the input stream. + * Write the HTTP/2 connection preface (RFC 9113 Section 3.4). + * This must be sent by the client before any frames. + */ + void writeConnectionPreface() throws IOException { + out.write(CONNECTION_PREFACE); + } + + // ==================== Stateful Parser API ==================== + + /** + * Read the next frame header and store state internally. + * + *

      After this call, use {@link #frameType()}, {@link #frameStreamId()}, + * {@link #frameFlags()}, {@link #framePayloadLength()}, and {@link #hasFrameFlag(int)} + * to access the current frame's metadata. + * + *

      The payload must be read via {@link #readPayloadInto(byte[], int, int)} or + * {@link #skipBytes(int)} before calling {@code nextFrame()} again. * - *

      Important: For control frames (SETTINGS, PING, WINDOW_UPDATE, RST_STREAM, - * PRIORITY, GOAWAY), the returned Frame's payload may reference a shared scratch buffer. - * The payload is only valid until the next call to {@code readFrame()}. Callers must - * parse control frames immediately before reading the next frame. + *

      This method uses zero-copy direct buffer access for frame header parsing, + * avoiding intermediate copies when possible. * - * @return the frame, or null if EOF + * @return frame type (0-255), or -1 on EOF * @throws IOException if reading fails or frame is malformed */ - Frame readFrame() throws IOException { - // Read 9-byte header - int read = readFully(readHeaderBuf, 0, FRAME_HEADER_SIZE); - if (read < FRAME_HEADER_SIZE) { - if (read == 0) { - return null; // EOF + int nextFrame() throws IOException { + // Zero-copy: ensure 9 bytes in buffer, then parse directly + if (!in.ensure(FRAME_HEADER_SIZE)) { + // EOF or incomplete header + if (in.buffered() == 0) { + return -1; // Clean EOF } - throw new IOException("Incomplete frame header: read " + read + " bytes"); + throw new IOException("Incomplete frame header: read " + in.buffered() + " bytes"); } - // Parse header - int length = ((readHeaderBuf[0] & 0xFF) << 16) | ((readHeaderBuf[1] & 0xFF) << 8) | (readHeaderBuf[2] & 0xFF); - int type = readHeaderBuf[3] & 0xFF; - int flags = readHeaderBuf[4] & 0xFF; - int streamId = ((readHeaderBuf[5] & 0x7F) << 24) // Mask off reserved bit - | ((readHeaderBuf[6] & 0xFF) << 16) - | ((readHeaderBuf[7] & 0xFF) << 8) - | (readHeaderBuf[8] & 0xFF); + // Parse header directly from input buffer (zero-copy) + byte[] buf = in.buffer(); + int p = in.position(); + + currentPayloadLength = ((buf[p] & 0xFF) << 16) + | ((buf[p + 1] & 0xFF) << 8) + | (buf[p + 2] & 0xFF); + currentType = buf[p + 3] & 0xFF; + currentFlags = buf[p + 4] & 0xFF; + currentStreamId = ((buf[p + 5] & 0x7F) << 24) // Mask off reserved bit + | ((buf[p + 6] & 0xFF) << 16) + | ((buf[p + 7] & 0xFF) << 8) + | (buf[p + 8] & 0xFF); + + in.consume(FRAME_HEADER_SIZE); // Validate frame size - if (length > maxFrameSize) { - throw new H2Exception(ERROR_FRAME_SIZE_ERROR, "Frame size " + length + " exceeds " + maxFrameSize); + if (currentPayloadLength > maxFrameSize) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "Frame size " + currentPayloadLength + " exceeds " + maxFrameSize); } // Validate stream ID requirements per RFC 9113 - validateStreamId(type, streamId); + validateStreamId(currentType, currentStreamId); // Validate fixed-size frame payloads per RFC 9113 - validateFrameSize(type, flags, length); - - // Read payload - use scratch buffer for control frames to avoid allocation - byte[] payload; - if (length == 0) { - payload = EMPTY_PAYLOAD; - } else if (isControlFrame(type) && length <= CONTROL_FRAME_SCRATCH_SIZE) { - // Control frames use scratch buffer (valid until next readFrame call) - read = readFully(controlFrameScratch, 0, length); - if (read < length) { - throw new IOException("Incomplete frame payload: expected " + length + ", read " + read); - } - payload = controlFrameScratch; - } else { - // DATA, HEADERS, CONTINUATION, PUSH_PROMISE, or large control frames - payload = new byte[length]; - read = readFully(payload, 0, length); - if (read < length) { - throw new IOException("Incomplete frame payload: expected " + length + ", read " + read); - } + validateFrameSize(currentType, currentFlags, currentPayloadLength); + + return currentType; + } + + /** + * Get the current frame's type. + * + * @return frame type (e.g., FRAME_TYPE_DATA, FRAME_TYPE_HEADERS) + */ + int frameType() { + return currentType; + } + + /** + * Get the current frame's flags. + * + * @return frame flags byte + */ + int frameFlags() { + return currentFlags; + } + + /** + * Get the current frame's stream ID. + * + * @return stream identifier (0 for connection-level frames) + */ + int frameStreamId() { + return currentStreamId; + } + + /** + * Get the current frame's payload length. + * + * @return payload length in bytes + */ + int framePayloadLength() { + return currentPayloadLength; + } + + /** + * Check if the current frame has a specific flag set. + * + * @param flag the flag to check (e.g., FLAG_END_STREAM) + * @return true if the flag is set + */ + boolean hasFrameFlag(int flag) { + return (currentFlags & flag) != 0; + } + + /** + * Check if there is more data buffered in the input stream. + * + *

      This is used for adaptive signaling: when processing DATA frames in a burst, + * we can defer waking the consumer thread if more frames are already buffered, + * reducing thread wakeup overhead. + * + * @return true if more data is immediately available without blocking + */ + boolean hasBufferedData() { + return in.buffered() > 0; + } + + // ==================== Payload Parsing Methods ==================== + + /** + * Parse SETTINGS frame payload. + * + * @param payload the payload buffer + * @param length the actual payload length + * @return array of {id, value} pairs + * @throws H2Exception if payload is invalid + */ + int[] parseSettings(byte[] payload, int length) throws H2Exception { + if (payload == null || length == 0) { + return new int[0]; + } + + // SETTINGS payload MUST be a multiple of 6 bytes (RFC 9113 Section 6.5) + if (length % 6 != 0) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "SETTINGS frame payload length " + length + " is not a multiple of 6"); + } + + int count = length / 6; + int[] settings = new int[count * 2]; + int pos = 0; + for (int i = 0; i < count; i++) { + int id = ((payload[pos] & 0xFF) << 8) | (payload[pos + 1] & 0xFF); + int value = ((payload[pos + 2] & 0xFF) << 24) + | ((payload[pos + 3] & 0xFF) << 16) + | ((payload[pos + 4] & 0xFF) << 8) + | (payload[pos + 5] & 0xFF); + settings[i * 2] = id; + settings[i * 2 + 1] = value; + pos += 6; + } + return settings; + } + + /** + * Parse GOAWAY frame payload. + * + * @param payload the payload buffer + * @param length the actual payload length + * @return {lastStreamId, errorCode} + * @throws H2Exception if payload is invalid + */ + int[] parseGoaway(byte[] payload, int length) throws H2Exception { + if (payload == null || length < 8) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, "GOAWAY frame payload too short: " + length); + } + + int lastStreamId = ((payload[0] & 0x7F) << 24) + | ((payload[1] & 0xFF) << 16) + | ((payload[2] & 0xFF) << 8) + | (payload[3] & 0xFF); + int errorCode = ((payload[4] & 0xFF) << 24) + | ((payload[5] & 0xFF) << 16) + | ((payload[6] & 0xFF) << 8) + | (payload[7] & 0xFF); + return new int[] {lastStreamId, errorCode}; + } + + /** + * Parse WINDOW_UPDATE frame payload. + * + * @param payload the payload buffer + * @param length the actual payload length + * @return window size increment + * @throws H2Exception if payload is invalid or increment is zero + */ + int parseWindowUpdate(byte[] payload, int length) throws H2Exception { + if (payload == null || length != 4) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "WINDOW_UPDATE frame must have 4-byte payload, got " + length); } - // Handle padding for DATA, HEADERS, and PUSH_PROMISE frames - if ((flags & FLAG_PADDED) != 0 - && (type == FRAME_TYPE_DATA || type == FRAME_TYPE_HEADERS || type == FRAME_TYPE_PUSH_PROMISE)) { - payload = removePadding(payload, type); - length = payload.length; // Update length after stripping padding - flags &= ~FLAG_PADDED; // Clear padded flag after processing + int increment = ((payload[0] & 0x7F) << 24) + | ((payload[1] & 0xFF) << 16) + | ((payload[2] & 0xFF) << 8) + | (payload[3] & 0xFF); + + if (increment == 0) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, "WINDOW_UPDATE increment must be non-zero"); } - // Handle PRIORITY in HEADERS frame - if (type == FRAME_TYPE_HEADERS && (flags & FLAG_PRIORITY) != 0) { - payload = removePriority(payload); - length = payload.length; // Update length after stripping priority - flags &= ~FLAG_PRIORITY; // Clear priority flag after processing + return increment; + } + + /** + * Read and parse WINDOW_UPDATE frame payload directly from stream. + * + *

      Uses zero-copy direct buffer access when possible. Reader thread only. + * + * @return window size increment + * @throws IOException if reading fails + * @throws H2Exception if payload is invalid or increment is zero + */ + int readAndParseWindowUpdate() throws IOException, H2Exception { + if (currentPayloadLength != 4) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "WINDOW_UPDATE frame must have 4-byte payload, got " + currentPayloadLength); + } + + // Zero-copy: ensure 4 bytes in buffer, then parse directly + if (!in.ensure(4)) { + throw new IOException("Unexpected EOF reading WINDOW_UPDATE payload"); } - return new Frame(type, flags, streamId, payload, length); + byte[] buf = in.buffer(); + int p = in.position(); + + int increment = ((buf[p] & 0x7F) << 24) + | ((buf[p + 1] & 0xFF) << 16) + | ((buf[p + 2] & 0xFF) << 8) + | (buf[p + 3] & 0xFF); + + in.consume(4); + + if (increment == 0) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, "WINDOW_UPDATE increment must be non-zero"); + } + + return increment; } /** - * Check if frame type is a control frame (processed immediately, payload doesn't escape). + * Parse RST_STREAM frame payload. + * + * @param payload the payload buffer + * @param length the actual payload length + * @return error code + * @throws H2Exception if payload is invalid */ - private static boolean isControlFrame(int type) { - return type == FRAME_TYPE_SETTINGS - || type == FRAME_TYPE_PING - || type == FRAME_TYPE_WINDOW_UPDATE - || type == FRAME_TYPE_RST_STREAM - || type == FRAME_TYPE_PRIORITY - || type == FRAME_TYPE_GOAWAY; + int parseRstStream(byte[] payload, int length) throws H2Exception { + if (payload == null || length != 4) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "RST_STREAM frame must have 4-byte payload, got " + length); + } + + return ((payload[0] & 0xFF) << 24) + | ((payload[1] & 0xFF) << 16) + | ((payload[2] & 0xFF) << 8) + | (payload[3] & 0xFF); + } + + /** + * Read and parse RST_STREAM frame payload directly from stream. + * + *

      Uses zero-copy direct buffer access when possible. Reader thread only. + * + * @return error code + * @throws IOException if reading fails + * @throws H2Exception if payload is invalid + */ + int readAndParseRstStream() throws IOException, H2Exception { + if (currentPayloadLength != 4) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "RST_STREAM frame must have 4-byte payload, got " + currentPayloadLength); + } + + // Zero-copy: ensure 4 bytes in buffer, then parse directly + if (!in.ensure(4)) { + throw new IOException("Unexpected EOF reading RST_STREAM payload"); + } + + byte[] buf = in.buffer(); + int p = in.position(); + + int errorCode = ((buf[p] & 0xFF) << 24) + | ((buf[p + 1] & 0xFF) << 16) + | ((buf[p + 2] & 0xFF) << 8) + | (buf[p + 3] & 0xFF); + + in.consume(4); + + return errorCode; } /** @@ -170,16 +388,18 @@ private static boolean isControlFrame(int type) { *

      Per RFC 9113 Section 4.3, a header block must be transmitted as a contiguous * sequence of frames with no interleaved frames of any other type or from any other stream. * - * @param initialFrame the initial HEADERS or PUSH_PROMISE frame + *

      This method uses the stateful parser API. The initial frame's header must have + * already been read via {@link #nextFrame()} and payload via {@link #readPayloadInto}. + * + * @param initialStreamId the stream ID from the initial HEADERS/PUSH_PROMISE frame + * @param initialPayload the payload from the initial frame + * @param initialLength the actual payload length * @return the complete header block payload * @throws IOException if reading fails */ - byte[] readHeaderBlock(Frame initialFrame) throws IOException { - byte[] initialPayload = initialFrame.payload(); - int initialLength = initialFrame.payloadLength(); - + byte[] readHeaderBlock(int initialStreamId, byte[] initialPayload, int initialLength) throws IOException { // For PUSH_PROMISE, strip the 4-byte promised stream ID to get the header block fragment - if (initialFrame.type() == FRAME_TYPE_PUSH_PROMISE && initialPayload != null) { + if (currentType == FRAME_TYPE_PUSH_PROMISE && initialPayload != null) { if (initialLength < 4) { throw new H2Exception(ERROR_FRAME_SIZE_ERROR, "PUSH_PROMISE frame payload too short for promised stream ID"); @@ -191,47 +411,62 @@ byte[] readHeaderBlock(Frame initialFrame) throws IOException { initialLength = fragmentLength; } - if (initialFrame.hasFlag(FLAG_END_HEADERS)) { - return initialPayload != null ? initialPayload : EMPTY_PAYLOAD; + if (hasFrameFlag(FLAG_END_HEADERS)) { + return initialPayload != null ? initialPayload : H2Constants.EMPTY_BYTES; } - // Need to read CONTINUATION frames - ByteArrayOutputStream headerBlock = new ByteArrayOutputStream(initialLength); + // Need to read CONTINUATION frames - use reusable buffer + headerBlockBuffer.reset(); if (initialPayload != null) { - headerBlock.write(initialPayload); + headerBlockBuffer.write(initialPayload, 0, initialLength); } while (true) { - Frame cont = readFrame(); - if (cont == null) { + int type = nextFrame(); + if (type < 0) { throw new IOException("EOF while reading CONTINUATION frames"); } // Per RFC 9113 Section 4.3: header block must be contiguous // Only CONTINUATION frames for the same stream are allowed - if (cont.type() != FRAME_TYPE_CONTINUATION) { + if (type != FRAME_TYPE_CONTINUATION) { throw new H2Exception(ERROR_PROTOCOL_ERROR, - "Header block interrupted by " + frameTypeName(cont.type()) + + "Header block interrupted by " + frameTypeName(type) + " frame (RFC 9113 Section 4.3 violation)"); } - if (cont.streamId() != initialFrame.streamId()) { + if (currentStreamId != initialStreamId) { throw new H2Exception(ERROR_PROTOCOL_ERROR, "CONTINUATION frame stream ID mismatch: expected " + - initialFrame.streamId() + ", got " + cont.streamId()); + initialStreamId + ", got " + currentStreamId); } - byte[] contPayload = cont.payload(); - if (contPayload != null) { - headerBlock.write(contPayload); + int contLength = currentPayloadLength; + if (contLength > 0) { + // Read directly into headerBlockBuffer to avoid intermediate allocation + readPayloadIntoBuffer(contLength); } - if (cont.hasFlag(FLAG_END_HEADERS)) { + if (hasFrameFlag(FLAG_END_HEADERS)) { break; } } - return headerBlock.toByteArray(); + // Return view into headerBlockBuffer - valid until next readHeaderBlock call + // Caller must process before next frame read (which is guaranteed since reader thread is single-threaded) + return headerBlockBuffer.array(); + } + + /** + * Get the size of the header block data after a readHeaderBlock call that used CONTINUATION frames. + * + *

      When readHeaderBlock returns headerBlockBuffer.array(), use this method to get the valid data length. + * Only valid when the previous readHeaderBlock result came from headerBlockBuffer (not the input payload). + * + * @return size of valid data in the header block buffer + */ + int headerBlockSize() { + return headerBlockBuffer.size(); } private void validateFrameSize(int type, int flags, int length) throws H2Exception { @@ -288,10 +523,34 @@ private void validateFrameSize(int type, int flags, int length) throws H2Excepti case FRAME_TYPE_PUSH_PROMISE: // PUSH_PROMISE must have at least 4 bytes for the promised stream ID // (plus 1 byte for pad length if PADDED flag is set) - int minLength = (flags & FLAG_PADDED) != 0 ? 5 : 4; - if (length < minLength) { + int pushMinLen = (flags & FLAG_PADDED) != 0 ? 5 : 4; + if (length < pushMinLen) { throw new H2Exception(ERROR_FRAME_SIZE_ERROR, - "PUSH_PROMISE frame must have at least " + minLength + "-byte payload, got " + length); + "PUSH_PROMISE frame must have at least " + pushMinLen + "-byte payload, got " + length); + } + break; + + case FRAME_TYPE_DATA: + // DATA frame with PADDED flag must have at least 1 byte (pad length) + if ((flags & FLAG_PADDED) != 0 && length < 1) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "DATA frame with PADDED flag must have at least 1-byte payload, got " + length); + } + break; + + case FRAME_TYPE_HEADERS: + // HEADERS with PADDED and/or PRIORITY flags need minimum payload sizes + int headersMinLen = 0; + if ((flags & FLAG_PADDED) != 0) { + headersMinLen += 1; // 1 byte for pad length + } + if ((flags & FLAG_PRIORITY) != 0) { + headersMinLen += 5; // 5 bytes for priority data + } + if (length < headersMinLen) { + throw new H2Exception(ERROR_FRAME_SIZE_ERROR, + "HEADERS frame with current flags must have at least " + headersMinLen + + "-byte payload, got " + length); } break; @@ -346,42 +605,6 @@ private void validateStreamId(int type, int streamId) throws H2Exception { } } - /** - * Remove padding from a padded frame payload. - */ - private byte[] removePadding(byte[] payload, int frameType) throws H2Exception { - if (payload.length < 1) { - throw new H2Exception(ERROR_FRAME_SIZE_ERROR, - "Padded " + frameTypeName(frameType) + " frame too short"); - } - - int padLength = payload[0] & 0xFF; - if (padLength >= payload.length) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, - "Pad length " + padLength + " exceeds payload length " + payload.length); - } - - int dataLength = payload.length - 1 - padLength; - byte[] data = new byte[dataLength]; - System.arraycopy(payload, 1, data, 0, dataLength); - return data; - } - - /** - * Remove PRIORITY fields from HEADERS frame payload. - */ - private byte[] removePriority(byte[] payload) throws H2Exception { - // PRIORITY adds 5 bytes: 4-byte stream dependency + 1-byte weight - if (payload.length < 5) { - throw new H2Exception(ERROR_FRAME_SIZE_ERROR, - "HEADERS frame with PRIORITY flag too short"); - } - - byte[] data = new byte[payload.length - 5]; - System.arraycopy(payload, 5, data, 0, data.length); - return data; - } - /** * Write a frame to the output stream. * @@ -399,7 +622,12 @@ void writeFrame(int type, int flags, int streamId, byte[] payload) throws IOExce * Write a frame to the output stream. * *

      This method is NOT synchronized. Callers must ensure exclusive access - * to the output stream (e.g., via H2Connection's writer thread). + * to the output stream (e.g., via H2Muxer's writer thread). + * + *

      The underlying UnsyncBufferedOutputStream will buffer small writes. + * For payloads larger than the buffer size, the header is buffered and the + * payload is written directly to the underlying stream. This is safe because + * the writer thread has exclusive access - no interleaving can occur. * * @param type frame type * @param flags frame flags @@ -422,10 +650,9 @@ void writeFrame( throw new IllegalArgumentException("Invalid stream ID: " + streamId); } - if (length > maxFrameSize) { - throw new H2Exception(ERROR_FRAME_SIZE_ERROR, - "Frame payload size " + length + " exceeds maximum " + maxFrameSize); - } + // Note: For outbound frames, the caller (H2Exchange.writeData) is responsible for + // chunking data according to the peer's MAX_FRAME_SIZE setting. We don't validate + // here because maxFrameSize is our receive limit, not the peer's. // Write header (using writeHeaderBuf - caller must ensure exclusive access) writeHeaderBuf[0] = (byte) ((length >> 16) & 0xFF); @@ -513,23 +740,41 @@ void writeSettingsAck() throws IOException { /** * Write GOAWAY frame. + * + *

      Debug data is written directly using writeAscii() to avoid allocation + * when the debug string is ASCII (the common case for error messages). */ void writeGoaway(int lastStreamId, int errorCode, String debugData) throws IOException { - byte[] debug = debugData != null ? debugData.getBytes(StandardCharsets.UTF_8) : EMPTY_PAYLOAD; - byte[] payload = new byte[8 + debug.length]; - - payload[0] = (byte) ((lastStreamId >> 24) & 0x7F); - payload[1] = (byte) ((lastStreamId >> 16) & 0xFF); - payload[2] = (byte) ((lastStreamId >> 8) & 0xFF); - payload[3] = (byte) (lastStreamId & 0xFF); - payload[4] = (byte) ((errorCode >> 24) & 0xFF); - payload[5] = (byte) ((errorCode >> 16) & 0xFF); - payload[6] = (byte) ((errorCode >> 8) & 0xFF); - payload[7] = (byte) (errorCode & 0xFF); - - System.arraycopy(debug, 0, payload, 8, debug.length); + int debugLen = debugData != null ? debugData.length() : 0; + int payloadLen = 8 + debugLen; + + // Write frame header manually to avoid allocating payload array + writeHeaderBuf[0] = (byte) ((payloadLen >> 16) & 0xFF); + writeHeaderBuf[1] = (byte) ((payloadLen >> 8) & 0xFF); + writeHeaderBuf[2] = (byte) (payloadLen & 0xFF); + writeHeaderBuf[3] = (byte) FRAME_TYPE_GOAWAY; + writeHeaderBuf[4] = 0; // flags + writeHeaderBuf[5] = 0; // stream ID = 0 + writeHeaderBuf[6] = 0; + writeHeaderBuf[7] = 0; + writeHeaderBuf[8] = 0; + out.write(writeHeaderBuf); - writeFrame(FRAME_TYPE_GOAWAY, 0, 0, payload); + // Write fixed 8-byte GOAWAY payload (lastStreamId + errorCode) using scratch buffer + writeScratch[0] = (byte) ((lastStreamId >> 24) & 0x7F); + writeScratch[1] = (byte) ((lastStreamId >> 16) & 0xFF); + writeScratch[2] = (byte) ((lastStreamId >> 8) & 0xFF); + writeScratch[3] = (byte) (lastStreamId & 0xFF); + writeScratch[4] = (byte) ((errorCode >> 24) & 0xFF); + writeScratch[5] = (byte) ((errorCode >> 16) & 0xFF); + writeScratch[6] = (byte) ((errorCode >> 8) & 0xFF); + writeScratch[7] = (byte) (errorCode & 0xFF); + out.write(writeScratch, 0, 8); + + // Write debug data directly as ASCII (avoids String.getBytes allocation) + if (debugLen > 0) { + out.writeAscii(debugData); + } } /** @@ -542,11 +787,11 @@ void writeWindowUpdate(int streamId, int windowSizeIncrement) throws IOException } // Use scratch buffer to avoid allocation - controlFrameScratch[0] = (byte) ((windowSizeIncrement >> 24) & 0x7F); - controlFrameScratch[1] = (byte) ((windowSizeIncrement >> 16) & 0xFF); - controlFrameScratch[2] = (byte) ((windowSizeIncrement >> 8) & 0xFF); - controlFrameScratch[3] = (byte) (windowSizeIncrement & 0xFF); - writeFrame(FRAME_TYPE_WINDOW_UPDATE, 0, streamId, controlFrameScratch, 0, 4); + writeScratch[0] = (byte) ((windowSizeIncrement >> 24) & 0x7F); + writeScratch[1] = (byte) ((windowSizeIncrement >> 16) & 0xFF); + writeScratch[2] = (byte) ((windowSizeIncrement >> 8) & 0xFF); + writeScratch[3] = (byte) (windowSizeIncrement & 0xFF); + writeFrame(FRAME_TYPE_WINDOW_UPDATE, 0, streamId, writeScratch, 0, 4); } /** @@ -555,11 +800,11 @@ void writeWindowUpdate(int streamId, int windowSizeIncrement) throws IOException */ void writeRstStream(int streamId, int errorCode) throws IOException { // Use scratch buffer to avoid allocation - controlFrameScratch[0] = (byte) ((errorCode >> 24) & 0xFF); - controlFrameScratch[1] = (byte) ((errorCode >> 16) & 0xFF); - controlFrameScratch[2] = (byte) ((errorCode >> 8) & 0xFF); - controlFrameScratch[3] = (byte) (errorCode & 0xFF); - writeFrame(FRAME_TYPE_RST_STREAM, 0, streamId, controlFrameScratch, 0, 4); + writeScratch[0] = (byte) ((errorCode >> 24) & 0xFF); + writeScratch[1] = (byte) ((errorCode >> 16) & 0xFF); + writeScratch[2] = (byte) ((errorCode >> 8) & 0xFF); + writeScratch[3] = (byte) (errorCode & 0xFF); + writeFrame(FRAME_TYPE_RST_STREAM, 0, streamId, writeScratch, 0, 4); } /** @@ -571,259 +816,140 @@ void flush() throws IOException { out.flush(); } - private int readFully(byte[] buf, int off, int len) throws IOException { - int total = 0; - while (total < len) { - int n = in.read(buf, off + total, len - total); - if (n < 0) { - break; - } - total += n; - } - return total; - } - /** * Read payload bytes directly into a provided buffer. * *

      This method is used by the reader thread to read DATA frame payloads * directly into an exchange's buffer, avoiding an intermediate allocation. * + *

      Uses zero-copy when the entire payload is already buffered. When partially + * buffered, drains the buffer then reads directly from the underlying stream + * to avoid redundant buffer fill/copy overhead. + * * @param dest the destination buffer * @param offset offset in the destination buffer * @param length number of bytes to read * @throws IOException if reading fails or EOF is reached before all bytes are read */ void readPayloadInto(byte[] dest, int offset, int length) throws IOException { - int read = readFully(dest, offset, length); - if (read < length) { - throw new IOException("Incomplete payload: expected " + length + ", read " + read); + // Fast path: if entirely buffered, single arraycopy (zero-copy from network perspective) + int buffered = in.buffered(); + if (length <= buffered) { + System.arraycopy(in.buffer(), in.position(), dest, offset, length); + in.consume(length); + return; + } + + // Drain what's buffered first + if (buffered > 0) { + System.arraycopy(in.buffer(), in.position(), dest, offset, buffered); + in.consume(buffered); + offset += buffered; + length -= buffered; + } + + // Read remainder directly from underlying stream (buffer is now empty). + // Using readDirect avoids the buffer fill/check overhead in read(). + while (length > 0) { + int n = in.readDirect(dest, offset, length); + if (n < 0) { + throw new IOException("Incomplete payload: unexpected EOF"); + } + offset += n; + length -= n; } } /** - * Read a single byte from the input stream. + * Read payload bytes directly into the headerBlockBuffer. * - *

      Used for reading pad length in padded DATA frames without allocating. + *

      Used when reading CONTINUATION frames to avoid allocating intermediate byte[] arrays. * - * @return the byte value (0-255) - * @throws IOException if reading fails or EOF is reached + * @param length number of bytes to read + * @throws IOException if reading fails or EOF is reached before all bytes are read */ - int readByte() throws IOException { - int b = in.read(); - if (b < 0) { - throw new IOException("Unexpected EOF reading byte"); + private void readPayloadIntoBuffer(int length) throws IOException { + // Fast path: if entirely buffered, write directly to headerBlockBuffer + int buffered = in.buffered(); + if (length <= buffered) { + headerBlockBuffer.write(in.buffer(), in.position(), length); + in.consume(length); + return; + } + + // Drain what's buffered first + if (buffered > 0) { + headerBlockBuffer.write(in.buffer(), in.position(), buffered); + in.consume(buffered); + length -= buffered; + } + + // Read remainder in chunks using scratch buffer + while (length > 0) { + int toRead = Math.min(length, writeScratch.length); + int totalRead = 0; + while (totalRead < toRead) { + int n = in.readDirect(writeScratch, totalRead, toRead - totalRead); + if (n < 0) { + throw new IOException("Incomplete payload: unexpected EOF"); + } + totalRead += n; + } + headerBlockBuffer.write(writeScratch, 0, totalRead); + length -= totalRead; } - return b; } /** - * Read the frame header only, without reading the payload. + * Read a single byte from the input stream. * - *

      This is used for DATA frames where we want to read the payload directly - * into the exchange buffer, avoiding an intermediate allocation. + *

      Used for reading pad length in padded DATA frames without allocating. + * Uses zero-copy direct buffer access when possible. * - * @return a FrameHeader with type, flags, streamId, and payload length, or null if EOF - * @throws IOException if reading fails or frame is malformed + * @return the byte value (0-255) + * @throws IOException if reading fails or EOF is reached */ - FrameHeader readFrameHeader() throws IOException { - // Read 9-byte header - int read = readFully(readHeaderBuf, 0, FRAME_HEADER_SIZE); - if (read < FRAME_HEADER_SIZE) { - if (read == 0) { - return null; // EOF - } - throw new IOException("Incomplete frame header: read " + read + " bytes"); - } - - // Parse header - int length = ((readHeaderBuf[0] & 0xFF) << 16) | ((readHeaderBuf[1] & 0xFF) << 8) | (readHeaderBuf[2] & 0xFF); - int type = readHeaderBuf[3] & 0xFF; - int flags = readHeaderBuf[4] & 0xFF; - int streamId = ((readHeaderBuf[5] & 0x7F) << 24) // Mask off reserved bit - | ((readHeaderBuf[6] & 0xFF) << 16) - | ((readHeaderBuf[7] & 0xFF) << 8) - | (readHeaderBuf[8] & 0xFF); - - // Validate frame size - if (length > maxFrameSize) { - throw new H2Exception(ERROR_FRAME_SIZE_ERROR, "Frame size " + length + " exceeds " + maxFrameSize); + int readByte() throws IOException { + if (!in.ensure(1)) { + throw new IOException("Unexpected EOF reading byte"); } - - return new FrameHeader(type, flags, streamId, length); + int b = in.buffer()[in.position()] & 0xFF; + in.consume(1); + return b; } /** * Skip the specified number of bytes in the input stream. * - *

      Used to skip past padding bytes in DATA frames. + *

      Used to skip past padding bytes in DATA frames. Uses direct buffer + * consume for small skips (common case), falling back to stream skip + * for larger amounts. * * @param length number of bytes to skip - * @throws IOException if skipping fails + * @throws IOException if skipping fails or EOF is reached before all bytes are skipped */ void skipBytes(int length) throws IOException { - int remaining = length; - while (remaining > 0) { - // Use scratch buffer for skipping - int toRead = Math.min(remaining, CONTROL_FRAME_SCRATCH_SIZE); - int read = readFully(controlFrameScratch, 0, toRead); - if (read < toRead) { - throw new IOException("Unexpected EOF while skipping " + length + " bytes"); - } - remaining -= toRead; - } - } - - /** - * Frame header information for deferred payload reading. - */ - record FrameHeader(int type, int flags, int streamId, int payloadLength) { - boolean hasFlag(int flag) { - return (flags & flag) != 0; - } - } - - /** - * Represents an HTTP/2 frame. - * - *

      Note: For control frames, the payload array may be a shared scratch buffer - * that is larger than the actual payload. Always use {@link #payloadLength()} to get - * the actual payload size, not {@code payload.length}. - * - * @param type frame type - * @param flags frame flags - * @param streamId stream identifier - * @param payload payload bytes (may be shared scratch buffer for control frames) - * @param length actual payload length (may be less than payload.length for scratch buffer) - */ - record Frame(int type, int flags, int streamId, byte[] payload, int length) { - - boolean hasFlag(int flag) { - return (flags & flag) != 0; - } - - int payloadLength() { - return length; + // Fast path: if entirely buffered, just consume (common for padding) + int buffered = in.buffered(); + if (length <= buffered) { + in.consume(length); + return; } - /** - * Parse SETTINGS frame payload. - * - * @return array of {id, value} pairs - * @throws H2Exception if frame is invalid - */ - int[] parseSettings() throws H2Exception { - if (type != FRAME_TYPE_SETTINGS) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, - "Expected SETTINGS frame, got " + frameTypeName(type)); - } - if (payload == null || length == 0) { - return new int[0]; - } - - // SETTINGS payload MUST be a multiple of 6 bytes (RFC 9113 Section 6.5) - if (length % 6 != 0) { - throw new H2Exception(ERROR_FRAME_SIZE_ERROR, - "SETTINGS frame payload length " + length + " is not a multiple of 6"); - } - - int count = length / 6; - int[] settings = new int[count * 2]; - int pos = 0; - for (int i = 0; i < count; i++) { - int id = ((payload[pos] & 0xFF) << 8) | (payload[pos + 1] & 0xFF); - int value = ((payload[pos + 2] & 0xFF) << 24) - | ((payload[pos + 3] & 0xFF) << 16) - | ((payload[pos + 4] & 0xFF) << 8) - | (payload[pos + 5] & 0xFF); - settings[i * 2] = id; - settings[i * 2 + 1] = value; - pos += 6; - } - return settings; - } - - /** - * Parse GOAWAY frame payload. - * - * @return {lastStreamId, errorCode} - * @throws H2Exception if frame is invalid - */ - int[] parseGoaway() throws H2Exception { - if (type != FRAME_TYPE_GOAWAY) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, "Expected GOAWAY frame, got " + frameTypeName(type)); - } else if (payload == null || length < 8) { - throw new H2Exception(ERROR_FRAME_SIZE_ERROR, "GOAWAY frame payload too short: " + length); - } - - int lastStreamId = ((payload[0] & 0x7F) << 24) - | ((payload[1] & 0xFF) << 16) - | ((payload[2] & 0xFF) << 8) - | (payload[3] & 0xFF); - int errorCode = ((payload[4] & 0xFF) << 24) - | ((payload[5] & 0xFF) << 16) - | ((payload[6] & 0xFF) << 8) - | (payload[7] & 0xFF); - return new int[] {lastStreamId, errorCode}; - } - - /** - * Parse WINDOW_UPDATE frame payload. - * - * @return window size increment - * @throws H2Exception if frame is invalid or increment is zero - */ - int parseWindowUpdate() throws H2Exception { - if (type != FRAME_TYPE_WINDOW_UPDATE) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, - "Expected WINDOW_UPDATE frame, got " + frameTypeName(type)); - } - if (payload == null || length != 4) { - throw new H2Exception(ERROR_FRAME_SIZE_ERROR, - "WINDOW_UPDATE frame must have 4-byte payload, got " + length); - } - - int increment = ((payload[0] & 0x7F) << 24) - | ((payload[1] & 0xFF) << 16) - | ((payload[2] & 0xFF) << 8) - | (payload[3] & 0xFF); - - if (increment == 0) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, "WINDOW_UPDATE increment must be non-zero"); - } - - return increment; + // Consume what's buffered + if (buffered > 0) { + in.consume(buffered); + length -= buffered; } - /** - * Parse RST_STREAM frame payload. - * - * @return error code - * @throws H2Exception if frame is invalid - */ - int parseRstStream() throws H2Exception { - if (type != FRAME_TYPE_RST_STREAM) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, - "Expected RST_STREAM frame, got " + frameTypeName(type)); - } - if (payload == null || length != 4) { - throw new H2Exception(ERROR_FRAME_SIZE_ERROR, - "RST_STREAM frame must have 4-byte payload, got " + length); + // Skip remainder in underlying stream + long remaining = length; + while (remaining > 0) { + long skipped = in.skip(remaining); + if (skipped <= 0) { + throw new IOException("Unexpected EOF while skipping bytes"); } - - return ((payload[0] & 0xFF) << 24) - | ((payload[1] & 0xFF) << 16) - | ((payload[2] & 0xFF) << 8) - | (payload[3] & 0xFF); - } - - @Override - public String toString() { - return String.format("Frame{type=%s, flags=0x%02x, streamId=%d, length=%d}", - frameTypeName(type), - flags, - streamId, - length); + remaining -= skipped; } } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java index 5233b8242..094ef8ce2 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java @@ -11,26 +11,19 @@ import static software.amazon.smithy.java.http.client.h2.H2Constants.FLAG_ACK; import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_DATA; import static software.amazon.smithy.java.http.client.h2.H2Constants.FRAME_TYPE_PING; -import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_AUTHORITY; -import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_METHOD; -import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_PATH; -import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_SCHEME; import java.io.IOException; import java.net.SocketTimeoutException; -import java.nio.ByteBuffer; import java.time.Duration; import java.util.ArrayList; -import java.util.Set; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.LockSupport; +import java.util.function.BiConsumer; import software.amazon.smithy.java.http.api.HttpHeaders; import software.amazon.smithy.java.http.api.HttpRequest; -import software.amazon.smithy.java.http.client.BufferPool; +import software.amazon.smithy.java.http.client.ByteAllocator; import software.amazon.smithy.java.http.client.h2.hpack.HpackEncoder; import software.amazon.smithy.java.io.ByteBufferOutputStream; @@ -63,45 +56,6 @@ interface ConnectionCallback { int getRemoteMaxHeaderListSize(); } - /** - * Work items processed by the writer thread. - */ - private sealed interface WorkItem { - record EncodeHeaders( - HttpRequest request, - H2Exchange exchange, - boolean endStream, - CompletableFuture streamIdFuture, - CompletableFuture writeComplete) implements WorkItem {} - - record WriteData( - int streamId, - byte[] data, - int offset, - int length, - int flags, - CompletableFuture completion) implements WorkItem {} - - record WriteTrailers( - int streamId, - HttpHeaders trailers, - CompletableFuture completion) implements WorkItem {} - - record WriteRst(int streamId, int errorCode) implements WorkItem {} - - record WriteGoaway(int lastStreamId, int errorCode, String debugData) implements WorkItem {} - - record WriteWindowUpdate(int streamId, int increment) implements WorkItem {} - - record WriteSettingsAck() implements WorkItem {} - - record WritePing(byte[] payload, boolean ack) implements WorkItem {} - - record Shutdown() implements WorkItem {} - - record CheckDataQueue() implements WorkItem {} - } - enum ControlFrameType { RST_STREAM, WINDOW_UPDATE, @@ -110,28 +64,16 @@ enum ControlFrameType { GOAWAY } - // Headers that must not be sent over HTTP/2 (connection-specific) - private static final Set CONNECTION_HEADERS = Set.of( - "connection", - "keep-alive", - "proxy-connection", - "transfer-encoding", - "upgrade", - "host"); - - // Headers that should not be indexed in HPACK (contain sensitive data) - private static final Set SENSITIVE_HEADERS = Set.of( - "authorization", - "cookie", - "proxy-authorization", - "set-cookie"); - - // How often to check for read timeouts (every ~100ms) - // This is also the resolution of the tick-based timeout system + // The resolution of the tick-based timeout system, used to check for read timeouts. static final int TIMEOUT_POLL_INTERVAL_MS = 100; - // Singleton wake-up signal - private static final WorkItem.CheckDataQueue CHECK_DATA_QUEUE = new WorkItem.CheckDataQueue(); + // Reusable singleton work items + private static final H2MuxerWorkItem.CheckDataQueue CHECK_DATA_QUEUE = H2MuxerWorkItem.CheckDataQueue.INSTANCE; + private static final H2MuxerWorkItem.Shutdown SHUTDOWN = H2MuxerWorkItem.Shutdown.INSTANCE; + private static final H2MuxerWorkItem.WriteSettingsAck SETTINGS_ACK = H2MuxerWorkItem.WriteSettingsAck.INSTANCE; + + // Static method reference to avoid allocation in hot timeout check path + private static final BiConsumer TIMEOUT_CHECKER = H2Muxer::checkExchangeTimeout; // === STREAM REGISTRY === private final StreamRegistry streams = new StreamRegistry(); @@ -145,7 +87,7 @@ enum ControlFrameType { private volatile int remoteMaxFrameSize = DEFAULT_MAX_FRAME_SIZE; // === CONNECTION FLOW CONTROL === - private final FlowControlWindow connectionSendWindow = new FlowControlWindow(DEFAULT_INITIAL_WINDOW_SIZE); + private final FlowControlWindow connectionSendWindow; // === STATE === private volatile boolean accepting = true; @@ -160,18 +102,18 @@ enum ControlFrameType { // === DEPENDENCIES === private final ConnectionCallback connectionCallback; private final H2FrameCodec frameCodec; - private final BufferPool bufferPool; + private final ByteAllocator allocator; private final int initialWindowSize; // === WORK QUEUES === - private final BlockingQueue workQueue; + // CLQ + LockSupport for lock-free work submission without DelayScheduler overhead + private final ConcurrentLinkedQueue workQueue = new ConcurrentLinkedQueue<>(); private final ConcurrentLinkedQueue dataWorkQueue = new ConcurrentLinkedQueue<>(); - private volatile boolean dataWorkPending; + private final AtomicBoolean dataWorkPending = new AtomicBoolean(false); - // === HPACK ENCODER (only accessed by writer thread) === - private final HpackEncoder hpackEncoder; - private final ByteBufferOutputStream headerEncodeBuffer; - private volatile int pendingTableSizeUpdate = -1; + // === HEADER ENCODER (only accessed by writer thread) === + private final H2RequestHeaderEncoder headerEncoder; + private final AtomicInteger pendingTableSizeUpdate = new AtomicInteger(-1); // === WRITER THREAD === private final Thread workerThread; @@ -195,10 +137,11 @@ enum ControlFrameType { this.connectionCallback = connectionCallback; this.frameCodec = frameCodec; this.initialWindowSize = initialWindowSize; - this.bufferPool = new BufferPool(32, initialWindowSize, initialWindowSize, 1024); - this.workQueue = new ArrayBlockingQueue<>(H2Constants.WRITER_QUEUE_CAPACITY); - this.hpackEncoder = new HpackEncoder(initialTableSize); - this.headerEncodeBuffer = new ByteBufferOutputStream(512); + this.connectionSendWindow = new FlowControlWindow(DEFAULT_INITIAL_WINDOW_SIZE); + this.allocator = new ByteAllocator(64, initialWindowSize, initialWindowSize, 1024); + this.headerEncoder = new H2RequestHeaderEncoder( + new HpackEncoder(initialTableSize), + new ByteBufferOutputStream(512)); this.workerThread = Thread.ofVirtual().name(threadName).start(this::workerLoop); } @@ -233,8 +176,9 @@ H2Exchange newExchange(HttpRequest request, long readTimeoutMs, long writeTimeou */ void closeExchanges(Duration timeout) { accepting = false; - - streams.forEach(null, (exchange, _ignore) -> exchange.signalConnectionClosed(null)); + streams.forEach(null, (exchange, _ignore) -> { + exchange.signalConnectionClosed(null); + }); long deadline = System.nanoTime() + timeout.toNanos(); while (activeStreamCount.get() > 0 && System.nanoTime() < deadline) { @@ -338,8 +282,8 @@ void onGoaway(int lastStreamId, int errorCode) { H2Exception refusedError = new H2Exception( errorCode, - "Stream affected by GOAWAY (lastStreamId=" + lastStreamId + - ", error=" + H2Constants.errorCodeName(errorCode) + ")"); + "Stream affected by GOAWAY (lastStreamId=" + lastStreamId + + ", error=" + H2Constants.errorCodeName(errorCode) + ")"); streams.forEachMatching( streamId -> streamId > lastStreamId, exchange -> exchange.signalConnectionClosed(refusedError)); @@ -349,7 +293,7 @@ void onGoaway(int lastStreamId, int errorCode) { void acquireConnectionWindow(int requestedBytes, long timeoutMs) throws SocketTimeoutException, InterruptedException { - if (!connectionSendWindow.tryAcquire(requestedBytes, timeoutMs, TimeUnit.MILLISECONDS)) { + if (!connectionSendWindow.tryAcquire(requestedBytes, timeoutMs)) { throw new SocketTimeoutException( "Write timed out after " + timeoutMs + "ms waiting for connection flow control window"); } @@ -369,90 +313,82 @@ void signalDataReady(H2Exchange exchange) { return; } dataWorkQueue.offer(exchange); - if (!dataWorkPending) { - dataWorkPending = true; - workQueue.offer(CHECK_DATA_QUEUE); + // CAS ensures only one thread enqueues CHECK_DATA_QUEUE per batch + if (dataWorkPending.compareAndSet(false, true)) { + enqueue(CHECK_DATA_QUEUE); } } - void queueControlFrame(int streamId, ControlFrameType frameType, Object payload, long timeoutMs) - throws IOException { - WorkItem item = switch (frameType) { - case RST_STREAM -> new WorkItem.WriteRst(streamId, (Integer) payload); - case WINDOW_UPDATE -> new WorkItem.WriteWindowUpdate(streamId, (Integer) payload); - case SETTINGS_ACK -> new WorkItem.WriteSettingsAck(); - case PING -> new WorkItem.WritePing((byte[]) payload, false); + /** + * Enqueue a work item with deadline and signal the writer. + */ + private void enqueue(H2MuxerWorkItem item, long timeoutMs) { + item.deadlineTick = deadlineTick(timeoutMs); + workQueue.add(item); + signalWriter(); + } + + /** + * Enqueue a work item without timeout and signal the writer. + */ + private void enqueue(H2MuxerWorkItem item) { + item.deadlineTick = 0; + workQueue.add(item); + signalWriter(); + } + + void queueControlFrame(int streamId, ControlFrameType frameType, Object payload, long timeoutMs) { + H2MuxerWorkItem item = switch (frameType) { + case RST_STREAM -> new H2MuxerWorkItem.WriteRst(streamId, (Integer) payload); + case WINDOW_UPDATE -> new H2MuxerWorkItem.WriteWindowUpdate(streamId, (Integer) payload); + case SETTINGS_ACK -> SETTINGS_ACK; + case PING -> new H2MuxerWorkItem.WritePing((byte[]) payload, false); case GOAWAY -> { Object[] args = (Object[]) payload; - yield new WorkItem.WriteGoaway((Integer) args[0], (Integer) args[1], (String) args[2]); + yield new H2MuxerWorkItem.WriteGoaway((Integer) args[0], (Integer) args[1], (String) args[2]); } }; - - try { - if (!workQueue.offer(item, timeoutMs, TimeUnit.MILLISECONDS)) { - throw new IOException("Work queue full, cannot queue control frame (timeout: " + timeoutMs + "ms)"); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IOException("Interrupted while queuing control frame", e); - } + enqueue(item, timeoutMs); } - void queueTrailers(int streamId, HttpHeaders trailers) throws IOException { - WorkItem item = new WorkItem.WriteTrailers(streamId, trailers, new CompletableFuture<>()); - if (!workQueue.offer(item)) { - throw new IOException("Work queue full, cannot queue trailers"); - } + void queueTrailers(int streamId, HttpHeaders trailers) { + enqueue(new H2MuxerWorkItem.WriteTrailers(streamId, trailers)); } - void queueData(int streamId, byte[] data, int offset, int length, int flags) throws IOException { - WorkItem item = new WorkItem.WriteData(streamId, data, offset, length, flags, new CompletableFuture<>()); - if (!workQueue.offer(item)) { - throw new IOException("Work queue full, cannot queue data frame"); - } + void queueData(int streamId, byte[] data, int offset, int length, int flags) { + enqueue(new H2MuxerWorkItem.WriteData(streamId, data, offset, length, flags)); } /** * Submit a HEADERS frame for encoding and writing. + * Always succeeds (CLQ is unbounded, bounded by stream slots). + * Timeout is enforced by watchdog sweep checking deadlineTick. + * + *

      After calling this method, the caller should call {@link H2Exchange#awaitWriteCompletion()} + * to block until the write completes, then read the stream ID from the exchange. * * @param request the HTTP request * @param exchange the exchange * @param endStream whether END_STREAM should be set - * @param streamIdFuture future completed with stream ID after allocation - * @param writeComplete future completed when write finishes - * @param timeoutMs timeout for queue submission - * @return true if submitted, false if queue full or not accepting + * @param timeoutMs timeout for write completion (checked by watchdog) + * @return true if submitted, false if not accepting */ - boolean submitHeaders( - HttpRequest request, - H2Exchange exchange, - boolean endStream, - CompletableFuture streamIdFuture, - CompletableFuture writeComplete, - long timeoutMs - ) { + boolean submitHeaders(HttpRequest request, H2Exchange exchange, boolean endStream, long timeoutMs) { if (!accepting) { return false; } - var item = new WorkItem.EncodeHeaders(request, exchange, endStream, streamIdFuture, writeComplete); - try { - return workQueue.offer(item, timeoutMs, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return false; - } + enqueue(new H2MuxerWorkItem.EncodeHeaders(request, exchange, endStream), timeoutMs); + return true; } - // ==================== BUFFER POOL ==================== + // ==================== BUFFER ALLOCATION ==================== byte[] borrowBuffer(int minSize) { - return bufferPool.borrow(minSize); + return allocator.borrow(minSize); } void returnBuffer(byte[] buffer) { - if (buffer != null) { - bufferPool.release(buffer); - } + allocator.release(buffer); } // ==================== SETTINGS ==================== @@ -477,8 +413,28 @@ int currentTimeoutTick() { return timeoutTick; } + /** + * Convert timeout in milliseconds to deadline tick. + * Returns 0 if timeoutMs <= 0 (no timeout). + */ + private int deadlineTick(long timeoutMs) { + if (timeoutMs <= 0) { + return 0; + } + int timeoutTicks = (int) Math.ceil((double) timeoutMs / TIMEOUT_POLL_INTERVAL_MS); + return timeoutTick + timeoutTicks; + } + + /** + * Signal the writer thread that work is available. + * Uses LockSupport.unpark which is safe to call even if thread isn't parked. + */ + private void signalWriter() { + LockSupport.unpark(workerThread); + } + void setMaxTableSize(int newSize) { - this.pendingTableSizeUpdate = newSize; + pendingTableSizeUpdate.set(newSize); } IOException getWriteError() { @@ -502,7 +458,7 @@ IOException getWriteError() { * timeouts are approximate and failure is recoverable at the caller layer. */ private void checkReadTimeouts(int tick) { - streams.forEach(tick, H2Muxer::checkExchangeTimeout); + streams.forEach(tick, TIMEOUT_CHECKER); } private static void checkExchangeTimeout(H2Exchange exchange, int nowTick) { @@ -528,31 +484,37 @@ private static void checkExchangeTimeout(H2Exchange exchange, int nowTick) { "Read timeout: no data received for " + exchange.getReadTimeoutMs() + "ms")); } + /** + * Check for write timeouts by examining the head of the work queue. + * If the head item has a deadline that has passed, fail the connection. + * Since items are processed in order, if head is stuck, everything is stuck. + */ + private void checkWriteTimeouts(int tick) { + H2MuxerWorkItem head = workQueue.peek(); + if (head != null && head.deadlineTick > 0 && tick >= head.deadlineTick) { + failWriter(new SocketTimeoutException( + "Write timeout: work item stuck in queue (deadline tick " + head.deadlineTick + + ", current tick " + tick + ")")); + } + } + // ==================== WRITER THREAD ==================== private void workerLoop() { - var batch = new ArrayList(64); + var batch = new ArrayList(64); IOException failure = null; long lastTimeoutCheck = System.currentTimeMillis(); try { while (running) { - WorkItem item = workQueue.take(); - - if (item instanceof WorkItem.Shutdown) { - return; - } - - if (!(item instanceof WorkItem.CheckDataQueue)) { - batch.add(item); - } - + // Drain all available work items from the queue + H2MuxerWorkItem item; while ((item = workQueue.poll()) != null) { - if (item instanceof WorkItem.Shutdown) { + if (item instanceof H2MuxerWorkItem.Shutdown) { processBatch(batch); return; } - if (!(item instanceof WorkItem.CheckDataQueue)) { + if (!(item instanceof H2MuxerWorkItem.CheckDataQueue)) { batch.add(item); } } @@ -561,7 +523,7 @@ private void workerLoop() { processBatch(batch); } - dataWorkPending = false; + dataWorkPending.set(false); boolean processedData = false; H2Exchange exchange; @@ -579,18 +541,18 @@ private void workerLoop() { } } - // Check for read timeouts periodically using tick-based system + // Check for timeouts periodically using tick-based system long now = System.currentTimeMillis(); if (now - lastTimeoutCheck >= TIMEOUT_POLL_INTERVAL_MS) { int tick = ++timeoutTick; checkReadTimeouts(tick); + checkWriteTimeouts(tick); lastTimeoutCheck = now; } - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - if (running) { - failure = new IOException("Writer thread interrupted", e); + + // Park until signaled or timeout interval elapses (for watchdog) + // LockSupport.parkNanos is VT-friendly and doesn't create DelayScheduler tasks + LockSupport.parkNanos(TIMEOUT_POLL_INTERVAL_MS * 1_000_000L); } } catch (Throwable t) { failure = new IOException("Writer thread crashed", t); @@ -633,21 +595,21 @@ private void processExchangePendingWrites(H2Exchange exchange) { } } - private void processBatch(ArrayList batch) { + private void processBatch(ArrayList batch) { if (batch.isEmpty()) { return; } try { - for (WorkItem item : batch) { + for (H2MuxerWorkItem item : batch) { processItem(item); } frameCodec.flush(); - for (WorkItem item : batch) { + for (H2MuxerWorkItem item : batch) { completeItem(item, null); } } catch (IOException e) { - for (WorkItem item : batch) { + for (H2MuxerWorkItem item : batch) { completeItem(item, e); } } finally { @@ -655,186 +617,71 @@ private void processBatch(ArrayList batch) { } } - private void processItem(WorkItem item) throws IOException { + private void processItem(H2MuxerWorkItem item) throws IOException { switch (item) { - case WorkItem.EncodeHeaders h -> processEncodeHeaders(h); - case WorkItem.WriteData d -> - frameCodec.writeFrame(FRAME_TYPE_DATA, d.flags(), d.streamId(), d.data(), d.offset(), d.length()); - case WorkItem.WriteTrailers t -> processWriteTrailers(t); - case WorkItem.WriteRst r -> frameCodec.writeRstStream(r.streamId(), r.errorCode()); - case WorkItem.WriteGoaway g -> frameCodec.writeGoaway(g.lastStreamId(), g.errorCode(), g.debugData()); - case WorkItem.WriteWindowUpdate w -> frameCodec.writeWindowUpdate(w.streamId(), w.increment()); - case WorkItem.WriteSettingsAck s -> frameCodec.writeSettingsAck(); - case WorkItem.WritePing p -> frameCodec.writeFrame(FRAME_TYPE_PING, p.ack() ? FLAG_ACK : 0, 0, p.payload()); - case WorkItem.Shutdown s -> { + case H2MuxerWorkItem.EncodeHeaders h -> processEncodeHeaders(h); + case H2MuxerWorkItem.WriteData d -> + frameCodec.writeFrame(FRAME_TYPE_DATA, d.flags, d.streamId, d.data, d.offset, d.length); + case H2MuxerWorkItem.WriteTrailers t -> processWriteTrailers(t); + case H2MuxerWorkItem.WriteRst r -> frameCodec.writeRstStream(r.streamId, r.errorCode); + case H2MuxerWorkItem.WriteGoaway g -> frameCodec.writeGoaway(g.lastStreamId, g.errorCode, g.debugData); + case H2MuxerWorkItem.WriteWindowUpdate w -> frameCodec.writeWindowUpdate(w.streamId, w.increment); + case H2MuxerWorkItem.WriteSettingsAck s -> frameCodec.writeSettingsAck(); + case H2MuxerWorkItem.WritePing p -> + frameCodec.writeFrame(FRAME_TYPE_PING, p.ack ? FLAG_ACK : 0, 0, p.payload); + case H2MuxerWorkItem.Shutdown s -> { } - case WorkItem.CheckDataQueue c -> { + case H2MuxerWorkItem.CheckDataQueue c -> { } } } - private void processEncodeHeaders(WorkItem.EncodeHeaders req) throws IOException { - H2Exchange exchange = req.exchange(); + private void processEncodeHeaders(H2MuxerWorkItem.EncodeHeaders req) throws IOException { + H2Exchange exchange = req.exchange; - try { - if (!connectionCallback.isAcceptingStreams()) { - req.streamIdFuture() - .completeExceptionally(new IOException("Connection is not accepting new streams")); - return; - } + if (!connectionCallback.isAcceptingStreams()) { + throw new IOException("Connection is not accepting new streams"); + } - int streamId = allocateAndRegisterStream(exchange); + int streamId = allocateAndRegisterStream(exchange); - int tableUpdate = pendingTableSizeUpdate; + try { + // Atomically read and clear to avoid losing updates from concurrent setMaxTableSize calls + int tableUpdate = pendingTableSizeUpdate.getAndSet(-1); if (tableUpdate >= 0) { - hpackEncoder.setMaxTableSize(tableUpdate); - pendingTableSizeUpdate = -1; + headerEncoder.setMaxTableSize(tableUpdate); } - byte[] headerBlock = encodeHeaders(req.request()); + headerEncoder.encodeHeaders(req.request, connectionCallback.getRemoteMaxHeaderListSize()); - exchange.onHeadersEncoded(req.endStream()); + exchange.onHeadersEncoded(req.endStream); + frameCodec.writeHeaders(streamId, headerEncoder.buffer(), 0, headerEncoder.size(), req.endStream); - frameCodec.writeHeaders(streamId, headerBlock, 0, headerBlock.length, req.endStream()); - - req.streamIdFuture().complete(streamId); + // Stream ID is already set on exchange by allocateAndRegisterStream + // Caller will read it after awaitWriteCompletion returns } catch (Exception e) { - int streamId = exchange.getStreamId(); - if (streamId > 0) { - releaseStream(streamId); - } - if (e instanceof IOException || e instanceof H2Exception) { - req.streamIdFuture().completeExceptionally(e); - } else { - req.streamIdFuture().completeExceptionally(new IOException("Encoding failed", e)); - } - } - } - - private void processWriteTrailers(WorkItem.WriteTrailers req) throws IOException { - byte[] headerBlock = encodeTrailers(req.trailers()); - frameCodec.writeHeaders(req.streamId(), headerBlock, 0, headerBlock.length, true); - } - - private byte[] encodeHeaders(HttpRequest request) throws IOException { - headerEncodeBuffer.reset(); - hpackEncoder.beginHeaderBlock(headerEncodeBuffer); - - long headerListSize = 0; - String method = request.method(); - boolean isConnect = "CONNECT".equalsIgnoreCase(method); - - String authority = getAuthority(request); - String scheme = isConnect ? null : request.uri().getScheme(); - String path = isConnect ? null : getPath(request); - - hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_METHOD, method, false); - headerListSize += PSEUDO_METHOD.length() + method.length() + 32; - - if (!isConnect) { - hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_SCHEME, scheme, false); - headerListSize += PSEUDO_SCHEME.length() + (scheme != null ? scheme.length() : 0) + 32; - } - - hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_AUTHORITY, authority, false); - headerListSize += PSEUDO_AUTHORITY.length() + authority.length() + 32; - - if (!isConnect) { - hpackEncoder.encodeHeader(headerEncodeBuffer, PSEUDO_PATH, path, false); - headerListSize += PSEUDO_PATH.length() + path.length() + 32; - } - - for (var entry : request.headers()) { - String name = entry.getKey(); - if (CONNECTION_HEADERS.contains(name)) { - continue; + releaseStream(streamId); + if (e instanceof IOException ioe) { + throw ioe; } - boolean isTe = "te".equals(name); - boolean sensitive = SENSITIVE_HEADERS.contains(name); - for (String value : entry.getValue()) { - if (isTe && !"trailers".equalsIgnoreCase(value)) { - continue; - } - hpackEncoder.encodeHeader(headerEncodeBuffer, name, value, sensitive); - headerListSize += name.length() + value.length() + 32; - } - } - - int maxSize = connectionCallback.getRemoteMaxHeaderListSize(); - if (maxSize != Integer.MAX_VALUE && headerListSize > maxSize) { - throw new IOException("Header list size (" + headerListSize + ") exceeds limit (" + maxSize + ")"); + throw new IOException("Encoding failed", e); } - - return finishHeaderBlock(); } - private byte[] encodeTrailers(HttpHeaders trailers) throws IOException { - headerEncodeBuffer.reset(); - hpackEncoder.beginHeaderBlock(headerEncodeBuffer); - - for (var entry : trailers) { - String name = entry.getKey(); - if (name.startsWith(":")) { - throw new IOException("Trailers must not contain pseudo-header: " + name); - } - boolean sensitive = SENSITIVE_HEADERS.contains(name); - for (String value : entry.getValue()) { - hpackEncoder.encodeHeader(headerEncodeBuffer, name, value, sensitive); - } - } - - return finishHeaderBlock(); + private void processWriteTrailers(H2MuxerWorkItem.WriteTrailers req) throws IOException { + headerEncoder.encodeTrailers(req.trailers); + frameCodec.writeHeaders(req.streamId, headerEncoder.buffer(), 0, headerEncoder.size(), true); } - private byte[] finishHeaderBlock() { - ByteBuffer buffer = headerEncodeBuffer.toByteBuffer(); - byte[] result = new byte[buffer.remaining()]; - buffer.get(result); - return result; - } - - private String getAuthority(HttpRequest request) { - String host = request.uri().getHost(); - int port = request.uri().getPort(); - String scheme = request.uri().getScheme(); - if (port == -1 || (port == 443 && "https".equalsIgnoreCase(scheme)) - || (port == 80 && "http".equalsIgnoreCase(scheme))) { - return host; - } - return host + ":" + port; - } - - private String getPath(HttpRequest request) { - String path = request.uri().getRawPath(); - if (path == null || path.isEmpty()) { - path = "/"; - } - String query = request.uri().getRawQuery(); - if (query != null && !query.isEmpty()) { - path = path + "?" + query; - } - return path; - } - - private void completeItem(WorkItem item, IOException error) { - CompletableFuture completion = switch (item) { - case WorkItem.EncodeHeaders h -> h.writeComplete(); - case WorkItem.WriteData d -> d.completion(); - case WorkItem.WriteTrailers t -> t.completion(); - case WorkItem.WriteRst r -> null; - case WorkItem.WriteGoaway g -> null; - case WorkItem.WriteWindowUpdate w -> null; - case WorkItem.WriteSettingsAck s -> null; - case WorkItem.WritePing p -> null; - case WorkItem.Shutdown s -> null; - case WorkItem.CheckDataQueue c -> null; - }; - if (completion != null) { + private void completeItem(H2MuxerWorkItem item, IOException error) { + // Get the exchange to signal (only EncodeHeaders has an exchange directly) + H2Exchange exchange = (item instanceof H2MuxerWorkItem.EncodeHeaders h) ? h.exchange : null; + if (exchange != null) { if (error == null) { - completion.complete(null); + exchange.signalWriteSuccess(); } else { - completion.completeExceptionally(error); + exchange.signalWriteFailure(error); } } } @@ -848,11 +695,8 @@ private void failWriter(IOException e) { } private void drainAndFailPending(IOException error) { - WorkItem item; + H2MuxerWorkItem item; while ((item = workQueue.poll()) != null) { - if (item instanceof WorkItem.EncodeHeaders h) { - h.streamIdFuture().completeExceptionally(error); - } completeItem(item, error); } } @@ -861,8 +705,12 @@ private void drainAndFailPending(IOException error) { public void close() { accepting = false; + // Signal writer to process remaining work before we shut down + signalWriter(); + long deadline = System.currentTimeMillis() + 1000; while (!workQueue.isEmpty() && System.currentTimeMillis() < deadline) { + signalWriter(); // Keep signaling in case writer parks between checks try { Thread.sleep(10); } catch (InterruptedException e) { @@ -872,7 +720,7 @@ public void close() { } running = false; - var _ignore = workQueue.offer(new WorkItem.Shutdown()); + enqueue(SHUTDOWN); if (workerThread != null) { workerThread.interrupt(); @@ -889,7 +737,7 @@ public void close() { void shutdownNow() { accepting = false; running = false; - var _ignore = workQueue.offer(new WorkItem.Shutdown()); + enqueue(SHUTDOWN); if (workerThread != null) { workerThread.interrupt(); } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2MuxerWorkItem.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2MuxerWorkItem.java new file mode 100644 index 000000000..cebd97d53 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2MuxerWorkItem.java @@ -0,0 +1,113 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; + +/** + * Work items processed by the writer thread. + * Base class includes deadlineTick for watchdog-based timeout, eliminating + * the need for a separate TimedWorkItem wrapper allocation. + */ +abstract sealed class H2MuxerWorkItem { + /** + * Deadline tick for timeout (0 = no timeout). Set before enqueueing. + */ + int deadlineTick; + + static final class EncodeHeaders extends H2MuxerWorkItem { + final HttpRequest request; + final H2Exchange exchange; + final boolean endStream; + + EncodeHeaders(HttpRequest request, H2Exchange exchange, boolean endStream) { + this.request = request; + this.exchange = exchange; + this.endStream = endStream; + } + } + + static final class WriteData extends H2MuxerWorkItem { + final int streamId; + final byte[] data; + final int offset; + final int length; + final int flags; + + WriteData(int streamId, byte[] data, int offset, int length, int flags) { + this.streamId = streamId; + this.data = data; + this.offset = offset; + this.length = length; + this.flags = flags; + } + } + + static final class WriteTrailers extends H2MuxerWorkItem { + final int streamId; + final HttpHeaders trailers; + + WriteTrailers(int streamId, HttpHeaders trailers) { + this.streamId = streamId; + this.trailers = trailers; + } + } + + static final class WriteRst extends H2MuxerWorkItem { + final int streamId; + final int errorCode; + + WriteRst(int streamId, int errorCode) { + this.streamId = streamId; + this.errorCode = errorCode; + } + } + + static final class WriteGoaway extends H2MuxerWorkItem { + final int lastStreamId; + final int errorCode; + final String debugData; + + WriteGoaway(int lastStreamId, int errorCode, String debugData) { + this.lastStreamId = lastStreamId; + this.errorCode = errorCode; + this.debugData = debugData; + } + } + + static final class WriteWindowUpdate extends H2MuxerWorkItem { + final int streamId; + final int increment; + + WriteWindowUpdate(int streamId, int increment) { + this.streamId = streamId; + this.increment = increment; + } + } + + static final class WriteSettingsAck extends H2MuxerWorkItem { + static final WriteSettingsAck INSTANCE = new WriteSettingsAck(); + } + + static final class WritePing extends H2MuxerWorkItem { + final byte[] payload; + final boolean ack; + + WritePing(byte[] payload, boolean ack) { + this.payload = payload; + this.ack = ack; + } + } + + static final class Shutdown extends H2MuxerWorkItem { + static final Shutdown INSTANCE = new Shutdown(); + } + + static final class CheckDataQueue extends H2MuxerWorkItem { + static final CheckDataQueue INSTANCE = new CheckDataQueue(); + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2RequestHeaderEncoder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2RequestHeaderEncoder.java new file mode 100644 index 000000000..884774fa6 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2RequestHeaderEncoder.java @@ -0,0 +1,221 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_AUTHORITY; +import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_METHOD; +import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_PATH; +import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_SCHEME; + +import java.io.IOException; +import java.util.Set; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.client.h2.hpack.HpackEncoder; +import software.amazon.smithy.java.io.ByteBufferOutputStream; + +/** + * Encodes HTTP/2 request headers and trailers using HPACK compression. + * + *

      This class handles the encoding of request HEADERS frames per RFC 9113, + * including pseudo-header construction, connection header filtering, and + * sensitive header marking. + * + *

      RFC 9113 Compliance

      + *
        + *
      • Section 8.3.1: Request pseudo-headers (:method, :scheme, :authority, :path)
      • + *
      • Section 8.2.2: CONNECT method handling (no :scheme or :path)
      • + *
      • Section 8.2.1: Connection-specific headers must not be sent
      • + *
      • Section 10.5.1: Header list size validation
      • + *
      + * + *

      Zero-Copy Design

      + *

      The encoder reuses an internal buffer across requests. After encoding, callers access + * the encoded data via {@link #buffer()} and {@link #size()} to avoid copying. The buffer + * is only valid until the next encode call. + * + *

      Threading

      + *

      This class is NOT thread-safe. It must only be used from the writer thread + * to maintain HPACK encoder state consistency. + */ +final class H2RequestHeaderEncoder { + + /** Headers that must not be sent over HTTP/2 (connection-specific per RFC 9113 Section 8.2.1). */ + private static final Set CONNECTION_HEADERS = Set.of( + "connection", + "keep-alive", + "proxy-connection", + "transfer-encoding", + "upgrade", + "host"); + + /** Headers that should not be indexed in HPACK (contain sensitive data). */ + private static final Set SENSITIVE_HEADERS = Set.of( + "authorization", + "cookie", + "proxy-authorization", + "set-cookie"); + + private final HpackEncoder hpackEncoder; + private final ByteBufferOutputStream encodeBuffer; + + /** + * Create a new request header encoder. + * + * @param hpackEncoder the HPACK encoder to use + * @param encodeBuffer the buffer to encode headers into + */ + H2RequestHeaderEncoder(HpackEncoder hpackEncoder, ByteBufferOutputStream encodeBuffer) { + this.hpackEncoder = hpackEncoder; + this.encodeBuffer = encodeBuffer; + } + + /** + * Set the maximum HPACK dynamic table size. + * + * @param maxSize the new maximum size + */ + void setMaxTableSize(int maxSize) { + hpackEncoder.setMaxTableSize(maxSize); + } + + /** + * Encode request headers into the internal buffer. + * + *

      After calling this method, use {@link #buffer()} and {@link #size()} to access the encoded data. + * The data is valid until the next encode call. + * + * @param request the HTTP request + * @param maxHeaderListSize maximum header list size allowed by peer (Integer.MAX_VALUE if unlimited) + * @throws IOException if encoding fails or header list size exceeds limit + */ + void encodeHeaders(HttpRequest request, int maxHeaderListSize) throws IOException { + encodeBuffer.reset(); + hpackEncoder.beginHeaderBlock(encodeBuffer); + + long headerListSize = 0; + String method = request.method(); + boolean isConnect = "CONNECT".equalsIgnoreCase(method); + + String authority = getAuthority(request); + String scheme = isConnect ? null : request.uri().getScheme(); + String path = isConnect ? null : getPath(request); + + // Encode pseudo-headers (must come first per RFC 9113 Section 8.3) + hpackEncoder.encodeHeader(encodeBuffer, PSEUDO_METHOD, method, false); + headerListSize += PSEUDO_METHOD.length() + method.length() + 32; + + if (!isConnect) { + hpackEncoder.encodeHeader(encodeBuffer, PSEUDO_SCHEME, scheme, false); + headerListSize += PSEUDO_SCHEME.length() + (scheme != null ? scheme.length() : 0) + 32; + } + + hpackEncoder.encodeHeader(encodeBuffer, PSEUDO_AUTHORITY, authority, false); + headerListSize += PSEUDO_AUTHORITY.length() + authority.length() + 32; + + if (!isConnect) { + hpackEncoder.encodeHeader(encodeBuffer, PSEUDO_PATH, path, false); + headerListSize += PSEUDO_PATH.length() + path.length() + 32; + } + + // Encode regular headers + for (var entry : request.headers()) { + String name = entry.getKey(); + if (CONNECTION_HEADERS.contains(name)) { + continue; + } + boolean isTe = "te".equals(name); + boolean sensitive = SENSITIVE_HEADERS.contains(name); + for (String value : entry.getValue()) { + // RFC 9113 Section 8.2.1: TE header may only contain "trailers" + if (isTe && !"trailers".equalsIgnoreCase(value)) { + continue; + } + hpackEncoder.encodeHeader(encodeBuffer, name, value, sensitive); + headerListSize += name.length() + value.length() + 32; + } + } + + // Validate header list size per RFC 9113 Section 10.5.1 + if (maxHeaderListSize != Integer.MAX_VALUE && headerListSize > maxHeaderListSize) { + throw new IOException( + "Header list size (" + headerListSize + ") exceeds limit (" + maxHeaderListSize + ")"); + } + } + + /** + * Encode trailer headers into the internal buffer. + * + *

      After calling this method, use {@link #buffer()} and {@link #size()} to access the encoded data. + * + * @param trailers the trailer headers + * @throws IOException if encoding fails or trailers contain pseudo-headers + */ + void encodeTrailers(HttpHeaders trailers) throws IOException { + encodeBuffer.reset(); + hpackEncoder.beginHeaderBlock(encodeBuffer); + + for (var entry : trailers) { + String name = entry.getKey(); + // RFC 9113 Section 8.1: Trailers MUST NOT contain pseudo-headers + if (name.startsWith(":")) { + throw new IOException("Trailers must not contain pseudo-header: " + name); + } + boolean sensitive = SENSITIVE_HEADERS.contains(name); + for (String value : entry.getValue()) { + hpackEncoder.encodeHeader(encodeBuffer, name, value, sensitive); + } + } + } + + /** + * Get the internal buffer containing encoded data. + * Valid from index 0 to {@link #size()} - 1. + * + * @return the internal buffer array + */ + byte[] buffer() { + return encodeBuffer.array(); + } + + /** + * Get the size of the encoded data in the buffer. + * + * @return number of valid bytes in {@link #buffer()} + */ + int size() { + return encodeBuffer.size(); + } + + /** + * Build the :authority pseudo-header value. + */ + private static String getAuthority(HttpRequest request) { + String host = request.uri().getHost(); + int port = request.uri().getPort(); + String scheme = request.uri().getScheme(); + if (port == -1 || (port == 443 && "https".equalsIgnoreCase(scheme)) + || (port == 80 && "http".equalsIgnoreCase(scheme))) { + return host; + } + return host + ":" + port; + } + + /** + * Build the :path pseudo-header value. + */ + private static String getPath(HttpRequest request) { + String path = request.uri().getRawPath(); + if (path == null || path.isEmpty()) { + path = "/"; + } + String query = request.uri().getRawQuery(); + if (query != null && !query.isEmpty()) { + path = path + "?" + query; + } + return path; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessor.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessor.java new file mode 100644 index 000000000..689a3804c --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessor.java @@ -0,0 +1,180 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import static software.amazon.smithy.java.http.client.h2.H2Constants.ERROR_PROTOCOL_ERROR; +import static software.amazon.smithy.java.http.client.h2.H2Constants.PSEUDO_STATUS; + +import java.io.IOException; +import java.util.List; +import java.util.Set; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.ModifiableHttpHeaders; +import software.amazon.smithy.java.http.client.h2.hpack.HeaderField; + +/** + * Processes HTTP/2 response headers and trailers with RFC 9113 validation. + * + *

      This class handles the validation and parsing of response HEADERS frames, + * including pseudo-header validation, Content-Length tracking, and trailer processing. + * + *

      RFC 9113 Compliance

      + *
        + *
      • Section 8.3: Pseudo-headers must appear before regular headers
      • + *
      • Section 8.3.2: Response must have exactly one :status pseudo-header
      • + *
      • Section 8.3: Request pseudo-headers not allowed in responses
      • + *
      • Section 8.1: Trailers must not contain pseudo-headers
      • + *
      • Section 8.1.1: Content-Length validation, 1xx responses must not have END_STREAM
      • + *
      + */ +final class H2ResponseHeaderProcessor { + + /** Request pseudo-headers (only allowed in requests, not responses). */ + private static final Set REQUEST_PSEUDO_HEADERS = Set.of( + ":method", + ":scheme", + ":authority", + ":path"); + + /** Result of processing response headers. */ + record Result(HttpHeaders headers, int statusCode, long contentLength) { + /** Indicates an informational (1xx) response that should be skipped. */ + static final Result INFORMATIONAL = new Result(null, -1, -1); + + boolean isInformational() { + return this == INFORMATIONAL; + } + } + + private H2ResponseHeaderProcessor() {} + + /** + * Process response headers with full RFC 9113 validation. + * + * @param fields the decoded header fields + * @param streamId the stream ID (for error messages) + * @param isEndStream whether END_STREAM flag was set + * @return the processing result, or {@link Result#INFORMATIONAL} for 1xx responses + * @throws H2Exception if headers violate RFC 9113 + * @throws IOException if headers are malformed + */ + static Result processResponseHeaders(List fields, int streamId, boolean isEndStream) + throws IOException { + ModifiableHttpHeaders headers = HttpHeaders.ofModifiable(); + int parsedStatusCode = -1; + boolean seenRegularHeader = false; + long contentLength = -1; + + for (HeaderField field : fields) { + String name = field.name(); + String value = field.value(); + + if (name.startsWith(":")) { + // RFC 9113 Section 8.3: All pseudo-headers MUST appear before regular headers + if (seenRegularHeader) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + streamId, + "Pseudo-header '" + name + "' appears after regular header"); + } + + if (name.equals(PSEUDO_STATUS)) { + // RFC 9113 Section 8.3.2: Response MUST have exactly one :status + if (parsedStatusCode != -1) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, streamId, "Expected a single :status header"); + } + try { + parsedStatusCode = Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new IOException("Invalid :status value: " + value); + } + } else if (REQUEST_PSEUDO_HEADERS.contains(name)) { + // RFC 9113 Section 8.3: Request pseudo-headers are NOT allowed in responses + throw new H2Exception(ERROR_PROTOCOL_ERROR, + streamId, + "Request pseudo-header '" + name + "' in response"); + } else { + // Unknown pseudo-header - RFC 9113 says endpoints MUST treat as malformed + throw new H2Exception(ERROR_PROTOCOL_ERROR, + streamId, + "Unknown pseudo-header '" + name + "' in response"); + } + } else { + // Handle a regular header + seenRegularHeader = true; + // Track Content-Length for validation per RFC 9113 Section 8.1.1 + if ("content-length".equals(name)) { + try { + long parsedLength = Long.parseLong(value); + if (contentLength != -1 && contentLength != parsedLength) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, streamId, "Multiple Content-Length values"); + } + contentLength = parsedLength; + } catch (NumberFormatException e) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, streamId, "Invalid Content-Length value: " + value); + } + } + + headers.addHeader(name, value); + } + } + + if (parsedStatusCode == -1) { + throw new IOException("Response missing :status pseudo-header"); + } + + // Check if this is an informational (1xx) response, and skip and wait for final response + if (parsedStatusCode >= 100 && parsedStatusCode < 200) { + if (isEndStream) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, streamId, "1xx response must not have END_STREAM"); + } + return Result.INFORMATIONAL; + } + + return new Result(headers, parsedStatusCode, contentLength); + } + + /** + * Process trailer headers per RFC 9113 Section 8.1. + * + *

      Trailers are HEADERS sent after DATA with END_STREAM. They MUST NOT + * contain pseudo-headers. + * + * @param fields the decoded header fields + * @param streamId the stream ID (for error messages) + * @return the trailer headers + * @throws H2Exception if trailers contain pseudo-headers + */ + static HttpHeaders processTrailers(List fields, int streamId) throws IOException { + ModifiableHttpHeaders trailers = HttpHeaders.ofModifiable(); + for (HeaderField field : fields) { + String name = field.name(); + // RFC 9113 Section 8.1: Trailers MUST NOT contain pseudo-headers + if (name.startsWith(":")) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, streamId, "Trailer contains pseudo-header '" + name + "'"); + } + trailers.addHeader(name, field.value()); + } + return trailers; + } + + /** + * Validate Content-Length matches actual data received per RFC 9113 Section 8.1.1. + * + * @param expectedContentLength expected content length (-1 if not specified) + * @param receivedContentLength actual bytes received + * @param streamId the stream ID (for error messages) + * @throws H2Exception if there is a mismatch + */ + static void validateContentLength(long expectedContentLength, long receivedContentLength, int streamId) + throws IOException { + if (expectedContentLength >= 0 && receivedContentLength != expectedContentLength) { + throw new H2Exception(ERROR_PROTOCOL_ERROR, + streamId, + "Content-Length mismatch: expected " + expectedContentLength + + " bytes, received " + receivedContentLength + " bytes"); + } + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamState.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamState.java new file mode 100644 index 000000000..497bdfd6d --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamState.java @@ -0,0 +1,357 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; + +/** + * Thread-safe packed state for an HTTP/2 stream. + * + *

      Encapsulates stream state machine, read state, status code, and flags in a single 32-bit integer. + * All state transitions use CAS operations for thread safety between the reader thread (which delivers + * response data) and the user's virtual thread (which reads the response). + * + *

      Bit Layout (32 bits total)

      + *
      + * [0-9]   (10 bits): StatusCode (0-1023). 0 means "not set" (-1 logic)
      + * [10]    (1 bit)  : ResponseHeadersReceived
      + * [11]    (1 bit)  : EndStreamReceived
      + * [12]    (1 bit)  : EndStreamSent
      + * [13-15] (3 bits) : ReadState (4 states, capacity for 8)
      + * [16-19] (4 bits) : StreamState (5 states, capacity for 16)
      + * [20-31] (12 bits): Reserved
      + * 
      + * + *

      Stream States (RFC 9113 Section 5.1)

      + *
        + *
      • IDLE: Initial state before HEADERS sent
      • + *
      • OPEN: Both sides can send data
      • + *
      • HALF_CLOSED_LOCAL: We sent END_STREAM, waiting for response
      • + *
      • HALF_CLOSED_REMOTE: They sent END_STREAM, we can still send
      • + *
      • CLOSED: Both sides done
      • + *
      + * + *

      Read States

      + *
        + *
      • WAITING: Waiting for response headers
      • + *
      • READING: Response headers received, reading body
      • + *
      • DONE: Response body complete (END_STREAM received)
      • + *
      • ERROR: An error occurred
      • + *
      + */ +final class H2StreamState { + + private static final int MASK_STATUS_CODE = 0x3FF; // 10 bits + private static final int FLAG_HEADERS_RECEIVED = 1 << 10; + private static final int FLAG_END_STREAM_RX = 1 << 11; + private static final int FLAG_END_STREAM_TX = 1 << 12; + + // ReadState constants (shift 13, 3 bits) + private static final int SHIFT_READ_STATE = 13; + private static final int MASK_READ_STATE = 0x7 << SHIFT_READ_STATE; + + // StreamState constants (shift 16, 4 bits) + private static final int SHIFT_STREAM_STATE = 16; + private static final int MASK_STREAM_STATE = 0xF << SHIFT_STREAM_STATE; + + /** Read state: waiting for response headers. */ + static final int RS_WAITING = 0; + /** Read state: response headers received, reading body. */ + static final int RS_READING = 1; + /** Read state: response body complete (END_STREAM received). */ + static final int RS_DONE = 2; + /** Read state: an error occurred. */ + static final int RS_ERROR = 3; + /** Stream state: initial state before HEADERS sent. */ + static final int SS_IDLE = 0; + /** Stream state: both sides can send data. */ + static final int SS_OPEN = 1; + /** Stream state: we sent END_STREAM, waiting for response. */ + static final int SS_HALF_CLOSED_LOCAL = 2; + /** Stream state: they sent END_STREAM, we can still send. */ + static final int SS_HALF_CLOSED_REMOTE = 3; + /** Stream state: both sides done. */ + static final int SS_CLOSED = 4; + + /** CAS VarHandle */ + private static final VarHandle STATE_HANDLE; + + static { + try { + STATE_HANDLE = MethodHandles.lookup().findVarHandle(H2StreamState.class, "packedState", int.class); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + // Initial: SS_IDLE, RS_WAITING, no flags, status=0 + private volatile int packedState = (SS_IDLE << SHIFT_STREAM_STATE) | (RS_WAITING << SHIFT_READ_STATE); + + /** + * Get the current read state. + * + * @return one of RS_WAITING, RS_READING, RS_DONE, RS_ERROR + */ + int getReadState() { + return (packedState & MASK_READ_STATE) >> SHIFT_READ_STATE; + } + + /** + * Get the current stream state. + * + * @return one of SS_IDLE, SS_OPEN, SS_HALF_CLOSED_LOCAL, SS_HALF_CLOSED_REMOTE, SS_CLOSED + */ + int getStreamState() { + return (packedState & MASK_STREAM_STATE) >> SHIFT_STREAM_STATE; + } + + /** + * Check if response headers have been received. + * + * @return true if response headers (final, not 1xx) have been received + */ + boolean isResponseHeadersReceived() { + return (packedState & FLAG_HEADERS_RECEIVED) != 0; + } + + /** + * Check if END_STREAM has been received from the remote peer. + * + * @return true if END_STREAM was received + */ + boolean isEndStreamReceived() { + return (packedState & FLAG_END_STREAM_RX) != 0; + } + + /** + * Check if END_STREAM has been sent to the remote peer. + * + * @return true if END_STREAM was sent + */ + boolean isEndStreamSent() { + return (packedState & FLAG_END_STREAM_TX) != 0; + } + + /** + * Get the HTTP status code from the response. + * + * @return the status code, or -1 if not yet received + */ + int getStatusCode() { + int code = packedState & MASK_STATUS_CODE; + return code == 0 ? -1 : code; // 0 means not set + } + + /** + * Atomically set response headers received with status code. + * Transitions read state from WAITING to READING if appropriate. + * + * @param statusCode the HTTP status code (100-999) + */ + void setResponseHeadersReceived(int statusCode) { + for (;;) { + int current = packedState; + int newState = current; + + // Set status code (clear old, set new) + newState &= ~MASK_STATUS_CODE; + newState |= (statusCode & MASK_STATUS_CODE); + + // Set headers received flag + newState |= FLAG_HEADERS_RECEIVED; + + // Transition read state: WAITING -> READING + int readState = (current & MASK_READ_STATE) >> SHIFT_READ_STATE; + if (readState == RS_WAITING) { + newState &= ~MASK_READ_STATE; + newState |= (RS_READING << SHIFT_READ_STATE); + } + + if (STATE_HANDLE.compareAndSet(this, current, newState)) { + return; + } + } + } + + /** + * Atomically mark end stream received. + * Updates read state to DONE and stream state appropriately. + */ + void markEndStreamReceived() { + for (;;) { + int current = packedState; + int newState = current | FLAG_END_STREAM_RX; + + // Set read state to DONE + newState &= ~MASK_READ_STATE; + newState |= (RS_DONE << SHIFT_READ_STATE); + + // Update stream state + int currentSS = (current & MASK_STREAM_STATE) >> SHIFT_STREAM_STATE; + int newSS = computeEndStreamTransition(currentSS, true); + if (newSS >= 0) { + newState &= ~MASK_STREAM_STATE; + newState |= (newSS << SHIFT_STREAM_STATE); + } + + if (STATE_HANDLE.compareAndSet(this, current, newState)) { + return; + } + } + } + + /** + * Atomically mark end stream sent. + * Updates stream state appropriately. + */ + void markEndStreamSent() { + for (;;) { + int current = packedState; + if ((current & FLAG_END_STREAM_TX) != 0) { + return; // Already set + } + + int newState = current | FLAG_END_STREAM_TX; + + // Update stream state + int currentSS = (current & MASK_STREAM_STATE) >> SHIFT_STREAM_STATE; + int newSS = computeEndStreamTransition(currentSS, false); + if (newSS >= 0) { + newState &= ~MASK_STREAM_STATE; + newState |= (newSS << SHIFT_STREAM_STATE); + } + + if (STATE_HANDLE.compareAndSet(this, current, newState)) { + return; + } + } + } + + /** + * Atomically set read state to DONE. + */ + void setReadStateDone() { + for (;;) { + int current = packedState; + int newState = current; + newState &= ~MASK_READ_STATE; + newState |= (RS_DONE << SHIFT_READ_STATE); + + if (STATE_HANDLE.compareAndSet(this, current, newState)) { + return; + } + } + } + + /** + * Atomically set stream state to CLOSED. + */ + void setStreamStateClosed() { + for (;;) { + int current = packedState; + int newState = current; + newState &= ~MASK_STREAM_STATE; + newState |= (SS_CLOSED << SHIFT_STREAM_STATE); + + if (STATE_HANDLE.compareAndSet(this, current, newState)) { + return; + } + } + } + + /** + * Atomically set error state: endStreamReceived flag + readState=ERROR. + * Does NOT update stream state (unlike markEndStreamReceived). + * Used for error paths where we want to signal the consumer without + * affecting protocol state machine. + */ + void setErrorState() { + setEndStreamFlagAndReadState(RS_ERROR); + } + + /** + * Atomically set endStreamReceived flag and readState=DONE. + * Does NOT update stream state - used by enqueueData where we're just + * recording that we've received all data, but stream state machine + * transitions happen elsewhere (handleHeadersEvent). + */ + void setEndStreamReceivedFlag() { + setEndStreamFlagAndReadState(RS_DONE); + } + + /** + * Called when headers are encoded and about to be sent. + * Atomically transitions stream state and optionally marks end stream sent. + * + * @param endStream true if END_STREAM flag is set on the HEADERS frame + */ + void onHeadersEncoded(boolean endStream) { + for (;;) { + int current = packedState; + int newState = current; + + if (endStream) { + newState |= FLAG_END_STREAM_TX; + newState &= ~MASK_STREAM_STATE; + newState |= (SS_HALF_CLOSED_LOCAL << SHIFT_STREAM_STATE); + } else { + newState &= ~MASK_STREAM_STATE; + newState |= (SS_OPEN << SHIFT_STREAM_STATE); + } + + if (STATE_HANDLE.compareAndSet(this, current, newState)) { + return; + } + } + } + + /** + * Atomically set endStreamReceived flag and the specified read state. + * Does NOT update stream state - used where we want to signal the consumer + * without affecting the H2 protocol state machine. + * + * @param readState the read state to set (RS_DONE or RS_ERROR) + */ + private void setEndStreamFlagAndReadState(int readState) { + for (;;) { + int current = packedState; + int newState = current | FLAG_END_STREAM_RX; + newState &= ~MASK_READ_STATE; + newState |= (readState << SHIFT_READ_STATE); + + if (STATE_HANDLE.compareAndSet(this, current, newState)) { + return; + } + } + } + + /** + * Compute the new stream state after an end-stream event. + * + * @param currentStreamState current stream state + * @param isReceived true if end-stream received, false if end-stream sent + * @return the new stream state, or -1 if no change needed + */ + private static int computeEndStreamTransition(int currentStreamState, boolean isReceived) { + if (isReceived) { + // End stream received: OPEN→HALF_CLOSED_REMOTE, HALF_CLOSED_LOCAL→CLOSED + if (currentStreamState == SS_OPEN) { + return SS_HALF_CLOSED_REMOTE; + } else if (currentStreamState == SS_HALF_CLOSED_LOCAL) { + return SS_CLOSED; + } + } else { + // End stream sent: OPEN→HALF_CLOSED_LOCAL, HALF_CLOSED_REMOTE→CLOSED + if (currentStreamState == SS_OPEN) { + return SS_HALF_CLOSED_LOCAL; + } else if (currentStreamState == SS_HALF_CLOSED_REMOTE) { + return SS_CLOSED; + } + } + return -1; // No change + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/PendingWrite.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/PendingWrite.java index 0792cdf3a..d78c8edfc 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/PendingWrite.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/PendingWrite.java @@ -10,7 +10,7 @@ */ final class PendingWrite { /** - * The data buffer (borrowed from BufferPool). + * The data buffer (borrowed from ByteAllocator). */ byte[] data; @@ -25,29 +25,26 @@ final class PendingWrite { int length; /** - * Whether this write has the END_STREAM flag. - */ - boolean endStream; - - /** - * Frame flags (e.g., END_STREAM). + * Frame flags for the DATA frame. Valid flags from {@link H2Constants}: + *
        + *
      • {@link H2Constants#FLAG_END_STREAM} (0x1) - Last frame for this stream
      • + *
      • {@link H2Constants#FLAG_PADDED} (0x8) - Frame is padded (not used)
      • + *
      */ int flags; /** * Initialize this pending write with data. * - * @param data the data buffer - * @param offset offset within buffer - * @param length length to write - * @param endStream whether this is the last write - * @param flags frame flags + * @param data the data buffer + * @param offset offset within buffer + * @param length length to write + * @param flags frame flags (see {@link H2Constants#FLAG_END_STREAM}) */ - void init(byte[] data, int offset, int length, boolean endStream, int flags) { + void init(byte[] data, int offset, int length, int flags) { this.data = data; this.offset = offset; this.length = length; - this.endStream = endStream; this.flags = flags; } @@ -58,7 +55,6 @@ void reset() { this.data = null; this.offset = 0; this.length = 0; - this.endStream = false; this.flags = 0; } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamRegistry.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamRegistry.java index 2ab56beff..537caef65 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamRegistry.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/StreamRegistry.java @@ -21,8 +21,10 @@ * occupied (by a long-lived stream), the new stream goes here. * * - *

      This guarantees that "Lapping" (ID wrap-around) never causes data loss, even if a stream stays open indefinitely - * while millions of others cycle through. + *

      The spillover mechanism handles slot collisions: since we map stream IDs to slots via modulo, a long-lived + * stream occupying a slot would block newer streams that hash to the same slot. The spillover map ensures these + * newer streams are still tracked. Note that HTTP/2 stream IDs never wrap around on the same connection - they + * monotonically increase until exhaustion (at which point the connection must be closed per RFC 9113). * *

      HTTP/2 client stream IDs have useful properties we exploit: *

        @@ -30,7 +32,8 @@ *
      • Monotonically increasing (never reused on same connection)
      • *
      * - *

      We map stream IDs to array slots via: {@code slot = ((streamId - 1) >>> 1) & slotMask}. This gives O(1) lookup + *

      We map stream IDs to array slots via: {@code slot = ((streamId - 1) >>> 1) & slotMask}. This converts + * odd IDs (1, 3, 5, ...) to sequential indices (0, 1, 2, ...) then masks to the slot range, giving O(1) lookup * without hashing or Integer boxing overhead. * *

      This class is a thread-safe registry and does not enforce any stream lifecycle policies @@ -39,7 +42,7 @@ final class StreamRegistry { // 4096 slots covers normal concurrency (100-1000) with ample headroom. - // Memory cost: 4096 * 4 bytes (ref) = 16KB per connection. + // Memory cost: 4096 * 4-8 bytes (ref) = 16-32KB per connection (depends on compressed oops). private static final int SLOTS = 4096; private static final int SLOT_MASK = SLOTS - 1; @@ -48,7 +51,7 @@ final class StreamRegistry { /** * Map stream ID to slot index. - * Stream IDs are odd (1, 3, 5, ...), so we divide by 2 to get compact indices. + * Stream IDs are odd (1, 3, 5, ...), so we subtract 1 and divide by 2 to get sequential indices (0, 1, 2, ...). */ private static int streamIdToSlot(int streamId) { return ((streamId - 1) >>> 1) & SLOT_MASK; diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoder.java index ee0d1d045..e474a2370 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoder.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoder.java @@ -7,8 +7,8 @@ import java.io.IOException; import java.io.OutputStream; -import java.nio.charset.StandardCharsets; import java.util.Set; +import software.amazon.smithy.java.io.ByteBufferOutputStream; /** * HPACK encoder for HTTP/2 header compression (RFC 7541). @@ -31,6 +31,10 @@ public final class HpackEncoder { // -1 means no update pending private int pendingTableSizeUpdate = -1; + // Reusable scratch buffer for string encoding to avoid per-string allocation. + // Typical header values are < 256 bytes; buffer grows if needed. + private byte[] stringBuf = new byte[256]; + /** * Create an encoder with the given maximum dynamic table size. * @@ -103,9 +107,7 @@ public void beginHeaderBlock(OutputStream out) throws IOException { * @param sensitive whether this header contains sensitive data * @throws IOException if encoding fails */ - public void encodeHeader(OutputStream out, String name, String value, boolean sensitive) - throws IOException { - + public void encodeHeader(OutputStream out, String name, String value, boolean sensitive) throws IOException { // Sensitive headers should never be indexed if (sensitive || NEVER_INDEX_HEADERS.contains(name)) { encodeLiteralNeverIndexed(out, name, value); @@ -148,15 +150,10 @@ private void encodeIndexed(OutputStream out, int index) throws IOException { } /** - * Encode a header as literal with indexing. - * Format: 01xxxxxx (6-bit prefix for index) + * Encode a header as literal with indexing. Format: 01xxxxxx (6-bit prefix for index) */ - private void encodeLiteralWithIndexing( - OutputStream out, - int nameIndex, - String name, - String value - ) throws IOException { + private void encodeLiteralWithIndexing(OutputStream out, int nameIndex, String name, String value) + throws IOException { if (nameIndex > 0) { // Indexed name encodeInteger(out, nameIndex, 6, 0x40); @@ -169,8 +166,7 @@ private void encodeLiteralWithIndexing( } /** - * Encode a header as literal never indexed. - * Format: 0001xxxx (4-bit prefix for index) + * Encode a header as literal never indexed. Format: 0001xxxx (4-bit prefix for index) */ private void encodeLiteralNeverIndexed(OutputStream out, int nameIndex, String name, String value) throws IOException { @@ -192,8 +188,7 @@ private void encodeLiteralNeverIndexed(OutputStream out, String name, String val } /** - * Encode an integer with the given prefix size. - * RFC 7541 Section 5.1 + * Encode an integer with the given prefix size. RFC 7541 Section 5.1 */ private void encodeInteger(OutputStream out, int value, int prefixBits, int prefix) throws IOException { int maxPrefix = (1 << prefixBits) - 1; @@ -215,23 +210,38 @@ private void encodeInteger(OutputStream out, int value, int prefixBits, int pref * Encode a string, using Huffman encoding if it saves space. * RFC 7541 Section 5.2 */ + @SuppressWarnings("deprecation") private void encodeString(OutputStream out, String str) throws IOException { - // Convert to bytes once and reuse for both length calculation and encoding - byte[] raw = str.getBytes(StandardCharsets.ISO_8859_1); + int len = str.length(); if (useHuffman) { - int huffmanLen = Huffman.encodedLength(raw); - if (huffmanLen < raw.length) { - // Use Huffman encoding - byte[] huffman = Huffman.encode(raw); - encodeInteger(out, huffman.length, 7, 0x80); // H=1 - out.write(huffman); - return; + // Need bytes in scratch buffer to calculate Huffman length + if (len > stringBuf.length) { + stringBuf = new byte[len]; + } + str.getBytes(0, len, stringBuf, 0); + + // Only use Huffman if it saves space. + int huffmanLen = Huffman.encodedLength(stringBuf, 0, len); + if (huffmanLen < len) { + encodeInteger(out, huffmanLen, 7, 0x80); // H=1 + Huffman.encode(stringBuf, 0, len, out); + } else { + encodeInteger(out, len, 7, 0x00); // H=0 + out.write(stringBuf, 0, len); + } + } else { + // Raw encoding, use optimized path for ByteBufferOutputStream if available. + encodeInteger(out, len, 7, 0x00); // H=0 + if (out instanceof ByteBufferOutputStream bbos) { + bbos.writeAscii(str); + } else { + if (len > stringBuf.length) { + stringBuf = new byte[len]; + } + str.getBytes(0, len, stringBuf, 0); + out.write(stringBuf, 0, len); } } - - // Use raw encoding - encodeInteger(out, raw.length, 7, 0x00); // H=0 - out.write(raw); } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/Huffman.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/Huffman.java index 3444f8114..f63ee2d48 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/Huffman.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/Huffman.java @@ -6,6 +6,7 @@ package software.amazon.smithy.java.http.client.h2.hpack; import java.io.IOException; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -563,30 +564,33 @@ private Huffman() {} private static final int[][] DECODE_TABLE = buildDecodeTable(); /** - * Encode bytes using Huffman coding. + * Encode bytes using Huffman coding directly to an output stream. + * + *

      This avoids allocating an intermediate byte[] buffer. * * @param data the bytes to encode - * @return Huffman-encoded bytes + * @param offset start offset in data + * @param length number of bytes to encode + * @param out the output stream to write to + * @throws IOException if writing fails */ - static byte[] encode(byte[] data) { - int capacity = encodedLength(data); - byte[] buf = new byte[capacity]; - int pos = 0; + static void encode(byte[] data, int offset, int length, OutputStream out) throws IOException { long current = 0; int bits = 0; - for (byte b : data) { - int index = b & 0xFF; + int end = offset + length; + for (int i = offset; i < end; i++) { + int index = data[i] & 0xFF; int code = CODES[index]; - int length = LENGTHS[index]; + int codeLength = LENGTHS[index]; - current <<= length; + current <<= codeLength; current |= code; - bits += length; + bits += codeLength; while (bits >= 8) { bits -= 8; - buf[pos++] = (byte) (current >> bits); + out.write((int) (current >> bits)); } } @@ -594,22 +598,23 @@ static byte[] encode(byte[] data) { if (bits > 0) { current <<= (8 - bits); current |= (EOS_CODE >> (EOS_LENGTH - (8 - bits))); - buf[pos++] = (byte) current; + out.write((int) current); } - - return (pos == capacity) ? buf : Arrays.copyOf(buf, pos); } /** - * Calculate the encoded length of a byte array without actually encoding it. + * Calculate the encoded length of a byte array region without actually encoding it. * * @param data the bytes to measure + * @param offset start offset in data + * @param length number of bytes to measure * @return length in bytes when Huffman-encoded */ - static int encodedLength(byte[] data) { + static int encodedLength(byte[] data, int offset, int length) { int bits = 0; - for (byte b : data) { - bits += LENGTHS[b & 0xFF]; + int end = offset + length; + for (int i = offset; i < end; i++) { + bits += LENGTHS[data[i] & 0xFF]; } return (bits + 7) / 8; } diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStreamTest.java index ebf2e44a7..507d1bbaa 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStreamTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/UnsyncBufferedInputStreamTest.java @@ -375,4 +375,243 @@ void throwsOnNegativeBufferSize() { assertThrows(IllegalArgumentException.class, () -> new UnsyncBufferedInputStream(delegate, -1)); } + + // ==================== Direct Buffer Access Tests ==================== + + @Test + void bufferReturnsInternalBuffer() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + // Trigger a read to fill the buffer + stream.read(); + + byte[] buf = stream.buffer(); + assertEquals(8, buf.length); // Buffer size we specified + } + + @Test + void positionAndLimitTrackBufferState() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + // Initially empty + assertEquals(0, stream.position()); + assertEquals(0, stream.limit()); + assertEquals(0, stream.buffered()); + + // After read, buffer is filled + stream.read(); + assertEquals(1, stream.position()); // Advanced by one read + assertEquals(5, stream.limit()); // All 5 bytes loaded + assertEquals(4, stream.buffered()); // 4 remaining + } + + @Test + void consumeAdvancesPosition() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + // Fill buffer + stream.read(); + int initialPos = stream.position(); + + stream.consume(2); + assertEquals(initialPos + 2, stream.position()); + assertEquals(2, stream.buffered()); + } + + @Test + void consumeThrowsOnOverflow() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + // Fill buffer + stream.read(); + + assertThrows(IndexOutOfBoundsException.class, () -> stream.consume(10)); + } + + @Test + void consumeThrowsOnNegative() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + stream.read(); + + assertThrows(IndexOutOfBoundsException.class, () -> stream.consume(-1)); + } + + @Test + void ensureReturnsTrueWhenDataAvailable() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + assertEquals(true, stream.ensure(5)); + // ensure() reads at least 5 bytes, may read more (up to buffer size) + assertEquals(true, stream.buffered() >= 5); + } + + @Test + void ensureCompactsAndFillsBuffer() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + // Fill buffer and consume some + stream.ensure(8); // Fill entire buffer [1,2,3,4,5,6,7,8] + stream.consume(6); // Now position=6, only 2 bytes left [7,8] + + // Ensure more than available - should compact and fill + assertEquals(true, stream.ensure(4)); + // After compacting, position should be 0 + assertEquals(0, stream.position()); + assertEquals(true, stream.buffered() >= 4); + + // Verify data integrity - after consuming bytes 1-6, next byte should be 7 + byte[] buf = stream.buffer(); + assertEquals(7, buf[0]); // First unread byte after consuming 1-6 + } + + @Test + void ensureReturnsFalseOnEof() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + // Try to ensure more bytes than available (but within buffer size) + assertEquals(false, stream.ensure(5)); + // But we should have whatever was available + assertEquals(3, stream.buffered()); + } + + @Test + void ensureThrowsWhenRequestExceedsBufferSize() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new UnsyncBufferedInputStream(delegate, 4); + + assertThrows(IllegalArgumentException.class, () -> stream.ensure(10)); + } + + @Test + void ensureReturnsTrueForZeroOrNegative() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + assertEquals(true, stream.ensure(0)); + assertEquals(true, stream.ensure(-5)); + } + + @Test + void ensureThrowsWhenClosed() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + stream.close(); + + assertThrows(IOException.class, () -> stream.ensure(1)); + } + + @Test + void readDirectBypassesBuffer() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + // Buffer is empty initially, so readDirect should work + byte[] buf = new byte[3]; + int n = stream.readDirect(buf, 0, 3); + assertEquals(3, n); + assertArrayEquals(new byte[] {1, 2, 3}, buf); + } + + @Test + void readDirectThrowsIfBufferNotEmpty() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + + // Fill the buffer by reading one byte + stream.read(); + + // Now buffer has data, readDirect should throw + assertThrows(IllegalStateException.class, () -> stream.readDirect(new byte[3], 0, 3)); + } + + @Test + void readDirectWorksAfterDrainingBuffer() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); + var stream = new UnsyncBufferedInputStream(delegate, 4); + + // Fill buffer [1,2,3,4] and consume all + stream.ensure(4); + stream.consume(4); + + // Buffer is now empty, readDirect should work + byte[] buf = new byte[4]; + int n = stream.readDirect(buf, 0, 4); + assertEquals(4, n); + assertArrayEquals(new byte[] {5, 6, 7, 8}, buf); + } + + @Test + void readDirectThrowsWhenClosed() throws IOException { + var delegate = new ByteArrayInputStream(new byte[] {1, 2, 3}); + var stream = new UnsyncBufferedInputStream(delegate, 8); + stream.close(); + + assertThrows(IOException.class, () -> stream.readDirect(new byte[3], 0, 3)); + } + + @Test + void directBufferAccessForZeroCopyParsing() throws IOException { + // Simulate zero-copy frame header parsing like H2FrameCodec does + byte[] frameData = { + 0, + 0, + 10, // length = 10 + 0, // type = DATA + 1, // flags = END_STREAM + 0, + 0, + 0, + 1, // stream ID = 1 + 'H', + 'e', + 'l', + 'l', + 'o', + ' ', + 'W', + 'o', + 'r', + 'l' // payload + }; + var delegate = new ByteArrayInputStream(frameData); + var stream = new UnsyncBufferedInputStream(delegate, 64); + + // Ensure 9-byte header is available + assertEquals(true, stream.ensure(9)); + + // Parse header directly from buffer (zero-copy) + byte[] buf = stream.buffer(); + int p = stream.position(); + + int length = ((buf[p] & 0xFF) << 16) + | ((buf[p + 1] & 0xFF) << 8) + | (buf[p + 2] & 0xFF); + int type = buf[p + 3] & 0xFF; + int flags = buf[p + 4] & 0xFF; + int streamId = ((buf[p + 5] & 0x7F) << 24) + | ((buf[p + 6] & 0xFF) << 16) + | ((buf[p + 7] & 0xFF) << 8) + | (buf[p + 8] & 0xFF); + + stream.consume(9); + + assertEquals(10, length); + assertEquals(0, type); + assertEquals(1, flags); + assertEquals(1, streamId); + + // Now read the payload + byte[] payload = new byte[length]; + assertEquals(length, stream.read(payload)); + assertEquals("Hello Worl", new String(payload, StandardCharsets.US_ASCII)); + } } diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/BufferPoolTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/ByteAllocatorTest.java similarity index 82% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/BufferPoolTest.java rename to http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/ByteAllocatorTest.java index d359c3782..72574b63b 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/BufferPoolTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/ByteAllocatorTest.java @@ -14,13 +14,13 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.client.BufferPool; +import software.amazon.smithy.java.http.client.ByteAllocator; -class BufferPoolTest { +class ByteAllocatorTest { @Test void borrowReturnsBufferOfRequestedSize() { - BufferPool pool = new BufferPool(10, 1024, 1024, 128); + ByteAllocator pool = new ByteAllocator(10, 1024, 1024, 128); byte[] buffer = pool.borrow(256); @@ -30,7 +30,7 @@ void borrowReturnsBufferOfRequestedSize() { @Test void borrowReturnsDefaultSizeWhenRequestedSizeIsSmaller() { - BufferPool pool = new BufferPool(10, 1024, 1024, 128); + ByteAllocator pool = new ByteAllocator(10, 1024, 1024, 128); byte[] buffer = pool.borrow(64); @@ -40,7 +40,7 @@ void borrowReturnsDefaultSizeWhenRequestedSizeIsSmaller() { @Test void releasedBufferCanBeReused() { - BufferPool pool = new BufferPool(10, 1024, 1024, 128); + ByteAllocator pool = new ByteAllocator(10, 1024, 1024, 128); byte[] buffer1 = pool.borrow(128); pool.release(buffer1); @@ -51,7 +51,7 @@ void releasedBufferCanBeReused() { @Test void poolSizeIncreasesOnRelease() { - BufferPool pool = new BufferPool(10, 1024, 1024, 128); + ByteAllocator pool = new ByteAllocator(10, 1024, 1024, 128); assertEquals(0, pool.size()); byte[] buffer = pool.borrow(128); @@ -62,7 +62,7 @@ void poolSizeIncreasesOnRelease() { @Test void poolSizeDecreasesOnBorrow() { - BufferPool pool = new BufferPool(10, 1024, 1024, 128); + ByteAllocator pool = new ByteAllocator(10, 1024, 1024, 128); byte[] buffer1 = pool.borrow(128); pool.release(buffer1); @@ -74,7 +74,7 @@ void poolSizeDecreasesOnBorrow() { @Test void poolRespectsMaxSize() { - BufferPool pool = new BufferPool(2, 1024, 1024, 128); + ByteAllocator pool = new ByteAllocator(2, 1024, 1024, 128); // Fill pool to max pool.release(new byte[128]); @@ -88,7 +88,7 @@ void poolRespectsMaxSize() { @Test void buffersLargerThanMaxPoolableSizeAreNotPooled() { - BufferPool pool = new BufferPool(10, 1024, 256, 128); + ByteAllocator pool = new ByteAllocator(10, 1024, 256, 128); byte[] largeBuffer = new byte[512]; // Larger than maxPoolableSize (256) pool.release(largeBuffer); @@ -98,7 +98,7 @@ void buffersLargerThanMaxPoolableSizeAreNotPooled() { @Test void borrowThrowsWhenRequestedSizeExceedsMaxBufferSize() { - BufferPool pool = new BufferPool(10, 256, 256, 128); + ByteAllocator pool = new ByteAllocator(10, 256, 256, 128); IllegalArgumentException ex = assertThrows( IllegalArgumentException.class, @@ -111,7 +111,7 @@ void borrowThrowsWhenRequestedSizeExceedsMaxBufferSize() { @Test void nullBufferIsIgnored() { - BufferPool pool = new BufferPool(10, 1024, 1024, 128); + ByteAllocator pool = new ByteAllocator(10, 1024, 1024, 128); pool.release(null); // Should not throw @@ -120,7 +120,7 @@ void nullBufferIsIgnored() { @Test void clearRemovesAllBuffers() { - BufferPool pool = new BufferPool(10, 1024, 1024, 128); + ByteAllocator pool = new ByteAllocator(10, 1024, 1024, 128); pool.release(new byte[128]); pool.release(new byte[128]); @@ -134,7 +134,7 @@ void clearRemovesAllBuffers() { @Test void tooSmallPooledBufferIsDropped() { - BufferPool pool = new BufferPool(10, 1024, 1024, 128); + ByteAllocator pool = new ByteAllocator(10, 1024, 1024, 128); // Release a small buffer byte[] smallBuffer = new byte[64]; @@ -150,27 +150,27 @@ void tooSmallPooledBufferIsDropped() { @Test void constructorValidatesMaxPoolCount() { - assertThrows(IllegalArgumentException.class, () -> new BufferPool(0, 1024, 1024, 128)); - assertThrows(IllegalArgumentException.class, () -> new BufferPool(-1, 1024, 1024, 128)); + assertThrows(IllegalArgumentException.class, () -> new ByteAllocator(0, 1024, 1024, 128)); + assertThrows(IllegalArgumentException.class, () -> new ByteAllocator(-1, 1024, 1024, 128)); } @Test void constructorValidatesDefaultBufferSize() { - assertThrows(IllegalArgumentException.class, () -> new BufferPool(10, 1024, 1024, 0)); - assertThrows(IllegalArgumentException.class, () -> new BufferPool(10, 1024, 1024, -1)); + assertThrows(IllegalArgumentException.class, () -> new ByteAllocator(10, 1024, 1024, 0)); + assertThrows(IllegalArgumentException.class, () -> new ByteAllocator(10, 1024, 1024, -1)); } @Test void constructorValidatesMaxPoolableSize() { // maxPoolableSize must be > 0 - assertThrows(IllegalArgumentException.class, () -> new BufferPool(10, 1024, 0, 128)); + assertThrows(IllegalArgumentException.class, () -> new ByteAllocator(10, 1024, 0, 128)); // maxPoolableSize must be <= maxBufferSize - assertThrows(IllegalArgumentException.class, () -> new BufferPool(10, 256, 512, 128)); + assertThrows(IllegalArgumentException.class, () -> new ByteAllocator(10, 256, 512, 128)); } @Test void borrowThrowsWhenMinSizeIsZeroOrNegative() { - BufferPool pool = new BufferPool(10, 1024, 1024, 128); + ByteAllocator pool = new ByteAllocator(10, 1024, 1024, 128); assertThrows(IllegalArgumentException.class, () -> pool.borrow(0)); assertThrows(IllegalArgumentException.class, () -> pool.borrow(-1)); @@ -178,7 +178,7 @@ void borrowThrowsWhenMinSizeIsZeroOrNegative() { @Test void lifoOrderPreserved() { - BufferPool pool = new BufferPool(10, 1024, 1024, 128); + ByteAllocator pool = new ByteAllocator(10, 1024, 1024, 128); byte[] buffer1 = new byte[128]; byte[] buffer2 = new byte[128]; @@ -196,7 +196,7 @@ void lifoOrderPreserved() { @Test void concurrentBorrowAndReleaseIsThreadSafe() throws InterruptedException { - BufferPool pool = new BufferPool(100, 1024, 1024, 128); + ByteAllocator pool = new ByteAllocator(100, 1024, 1024, 128); int threadCount = 10; int operationsPerThread = 1000; diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameCodecTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameCodecTest.java index 32d79c639..37d2e5fb5 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameCodecTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameCodecTest.java @@ -15,14 +15,45 @@ import java.io.IOException; import java.util.Arrays; import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.client.UnsyncBufferedInputStream; +import software.amazon.smithy.java.http.client.UnsyncBufferedOutputStream; class H2FrameCodecTest { + // Test helper record that simulates the old Frame record for testing + record TestFrame(int type, int flags, int streamId, byte[] payload, int length, H2FrameCodec codec) { + boolean hasFlag(int flag) { + return (flags & flag) != 0; + } + + int payloadLength() { + return length; + } + + int[] parseSettings() throws H2Exception { + return codec.parseSettings(payload, length); + } + + int[] parseGoaway() throws H2Exception { + return codec.parseGoaway(payload, length); + } + + int parseWindowUpdate() throws H2Exception { + return codec.parseWindowUpdate(payload, length); + } + + int parseRstStream() throws H2Exception { + return codec.parseRstStream(payload, length); + } + } + // Write helper methods @Test void writeSettings() throws IOException { var out = new ByteArrayOutputStream(); - codec(out).writeSettings(1, 4096, 3, 100); + var c = codec(out); + c.writeSettings(1, 4096, 3, 100); + c.flush(); var frame = decode(out); int[] s = frame.parseSettings(); @@ -36,7 +67,9 @@ void writeSettings() throws IOException { @Test void writeSettingsAck() throws IOException { var out = new ByteArrayOutputStream(); - codec(out).writeSettingsAck(); + var c = codec(out); + c.writeSettingsAck(); + c.flush(); var frame = decode(out); assertEquals(4, frame.type()); @@ -48,7 +81,9 @@ void writeSettingsAck() throws IOException { @Test void writeGoaway() throws IOException { var out = new ByteArrayOutputStream(); - codec(out).writeGoaway(5, 2, "debug"); + var c = codec(out); + c.writeGoaway(5, 2, "debug"); + c.flush(); var frame = decode(out); int[] g = frame.parseGoaway(); @@ -59,7 +94,9 @@ void writeGoaway() throws IOException { @Test void writeGoawayNullDebug() throws IOException { var out = new ByteArrayOutputStream(); - codec(out).writeGoaway(1, 0, null); + var c = codec(out); + c.writeGoaway(1, 0, null); + c.flush(); var frame = decode(out); assertEquals(7, frame.type()); @@ -70,7 +107,9 @@ void writeGoawayNullDebug() throws IOException { @Test void writeWindowUpdate() throws IOException { var out = new ByteArrayOutputStream(); - codec(out).writeWindowUpdate(1, 65535); + var c = codec(out); + c.writeWindowUpdate(1, 65535); + c.flush(); var frame = decode(out); assertEquals(65535, frame.parseWindowUpdate()); @@ -79,7 +118,9 @@ void writeWindowUpdate() throws IOException { @Test void writeRstStream() throws IOException { var out = new ByteArrayOutputStream(); - codec(out).writeRstStream(1, 8); + var c = codec(out); + c.writeRstStream(1, 8); + c.flush(); var frame = decode(out); assertEquals(8, frame.parseRstStream()); @@ -88,23 +129,30 @@ void writeRstStream() throws IOException { @Test void writeHeadersWithContinuation() throws IOException { var out = new ByteArrayOutputStream(); - var codec = new H2FrameCodec(new ByteArrayInputStream(new byte[0]), out, 16); + var codec = new H2FrameCodec(wrapIn(new byte[0]), wrapOut(out), 16); byte[] block = new byte[50]; codec.writeHeaders(1, block, 0, 50, true); codec.flush(); - var readCodec = - new H2FrameCodec(new ByteArrayInputStream(out.toByteArray()), new ByteArrayOutputStream(), 16384); - var frame = readCodec.readFrame(); - byte[] result = readCodec.readHeaderBlock(frame); + var readCodec = new H2FrameCodec(wrapIn(out.toByteArray()), wrapOut(new ByteArrayOutputStream()), 16384); + int type = readCodec.nextFrame(); + assertEquals(1, type); // HEADERS + int streamId = readCodec.frameStreamId(); + int length = readCodec.framePayloadLength(); + byte[] payload = new byte[length]; + readCodec.readPayloadInto(payload, 0, length); + readCodec.readHeaderBlock(streamId, payload, length); - assertEquals(50, result.length); + // Zero-copy: use headerBlockSize() for valid length + assertEquals(50, readCodec.headerBlockSize()); } @Test void writeHeadersSingleFrame() throws IOException { var out = new ByteArrayOutputStream(); - codec(out).writeHeaders(1, new byte[] {1, 2, 3}, 0, 3, false); + var c = codec(out); + c.writeHeaders(1, new byte[] {1, 2, 3}, 0, 3, false); + c.flush(); var frame = decode(out); assertTrue(frame.hasFlag(0x04)); // END_HEADERS @@ -117,12 +165,10 @@ void throwsOnNegativeStreamId() { () -> codec(new ByteArrayOutputStream()).writeFrame(0, 0, -1, new byte[0])); } - @Test - void throwsOnPayloadExceedsMax() { - var codec = new H2FrameCodec(new ByteArrayInputStream(new byte[0]), new ByteArrayOutputStream(), 100); - - assertThrows(H2Exception.class, () -> codec.writeFrame(0, 0, 1, new byte[200])); - } + // Note: We no longer validate outbound frame size in writeFrame() because + // the peer's MAX_FRAME_SIZE setting may be larger than our receive limit. + // The caller (H2Exchange.writeData) is responsible for chunking according + // to the peer's advertised MAX_FRAME_SIZE. @Test void throwsOnWindowUpdateZero() { @@ -141,11 +187,18 @@ void readHeaderBlockWithContinuation() throws IOException { out.write(buildFrame(1, 0, 1, new byte[] {1, 2})); // HEADERS no END_HEADERS out.write(buildFrame(9, 0x04, 1, new byte[] {3, 4})); // CONTINUATION with END_HEADERS - var codec = new H2FrameCodec(new ByteArrayInputStream(out.toByteArray()), new ByteArrayOutputStream(), 16384); - var frame = codec.readFrame(); - byte[] block = codec.readHeaderBlock(frame); + var codec = new H2FrameCodec(wrapIn(out.toByteArray()), wrapOut(new ByteArrayOutputStream()), 16384); + int type = codec.nextFrame(); + assertEquals(1, type); // HEADERS + int streamId = codec.frameStreamId(); + int length = codec.framePayloadLength(); + byte[] payload = new byte[length]; + codec.readPayloadInto(payload, 0, length); + byte[] block = codec.readHeaderBlock(streamId, payload, length); + int blockSize = codec.headerBlockSize(); - assertArrayEquals(new byte[] {1, 2, 3, 4}, block); + // Zero-copy: block is a view into internal buffer, use headerBlockSize() for valid length + assertArrayEquals(new byte[] {1, 2, 3, 4}, Arrays.copyOf(block, blockSize)); } @Test @@ -153,9 +206,16 @@ void throwsOnContinuationWrongStream() throws IOException { var out = new ByteArrayOutputStream(); out.write(buildFrame(1, 0, 1, new byte[] {1})); out.write(buildFrame(9, 0x04, 2, new byte[] {2})); // wrong stream - var codec = new H2FrameCodec(new ByteArrayInputStream(out.toByteArray()), new ByteArrayOutputStream(), 16384); + var codec = new H2FrameCodec(wrapIn(out.toByteArray()), wrapOut(new ByteArrayOutputStream()), 16384); - assertThrows(H2Exception.class, () -> codec.readHeaderBlock(codec.readFrame())); + assertThrows(H2Exception.class, () -> { + int type = codec.nextFrame(); + int streamId = codec.frameStreamId(); + int length = codec.framePayloadLength(); + byte[] payload = new byte[length]; + codec.readPayloadInto(payload, 0, length); + codec.readHeaderBlock(streamId, payload, length); + }); } @Test @@ -163,67 +223,59 @@ void throwsOnNonContinuationInterrupt() throws IOException { var out = new ByteArrayOutputStream(); out.write(buildFrame(1, 0, 1, new byte[] {1})); out.write(buildFrame(0, 0, 1, new byte[] {2})); // DATA interrupts - var codec = new H2FrameCodec(new ByteArrayInputStream(out.toByteArray()), new ByteArrayOutputStream(), 16384); + var codec = new H2FrameCodec(wrapIn(out.toByteArray()), wrapOut(new ByteArrayOutputStream()), 16384); - assertThrows(H2Exception.class, () -> codec.readHeaderBlock(codec.readFrame())); + assertThrows(H2Exception.class, () -> { + int type = codec.nextFrame(); + int streamId = codec.frameStreamId(); + int length = codec.framePayloadLength(); + byte[] payload = new byte[length]; + codec.readPayloadInto(payload, 0, length); + codec.readHeaderBlock(streamId, payload, length); + }); } @Test void throwsOnEofDuringContinuation() throws IOException { var out = new ByteArrayOutputStream(); out.write(buildFrame(1, 0, 1, new byte[] {1})); - var codec = new H2FrameCodec(new ByteArrayInputStream(out.toByteArray()), new ByteArrayOutputStream(), 16384); + var codec = new H2FrameCodec(wrapIn(out.toByteArray()), wrapOut(new ByteArrayOutputStream()), 16384); - assertThrows(IOException.class, () -> codec.readHeaderBlock(codec.readFrame())); + assertThrows(IOException.class, () -> { + int type = codec.nextFrame(); + int streamId = codec.frameStreamId(); + int length = codec.framePayloadLength(); + byte[] payload = new byte[length]; + codec.readPayloadInto(payload, 0, length); + codec.readHeaderBlock(streamId, payload, length); + }); } @Test void readHeaderBlockFromPushPromise() throws IOException { - byte[] payload = {0, 0, 0, 2, 'a', 'b'}; - var codec = new H2FrameCodec(new ByteArrayInputStream(buildFrame(5, 0x04, 1, payload)), - new ByteArrayOutputStream(), + byte[] framePayload = {0, 0, 0, 2, 'a', 'b'}; + var codec = new H2FrameCodec(wrapIn(buildFrame(5, 0x04, 1, framePayload)), + wrapOut(new ByteArrayOutputStream()), 16384); - byte[] block = codec.readHeaderBlock(codec.readFrame()); + int type = codec.nextFrame(); + assertEquals(5, type); // PUSH_PROMISE + int streamId = codec.frameStreamId(); + int length = codec.framePayloadLength(); + byte[] payload = new byte[length]; + codec.readPayloadInto(payload, 0, length); + byte[] block = codec.readHeaderBlock(streamId, payload, length); assertArrayEquals(new byte[] {'a', 'b'}, block); } - // Padding/Priority edge cases - @Test - void throwsOnPadLengthExceedsPayload() { - byte[] payload = {10, 'a'}; - - assertThrows(H2Exception.class, () -> decode(buildFrame(0, 0x08, 1, payload))); - } + // Padding validation note: With the stateful API, padding processing is the caller's + // responsibility. The codec validates minimum payload size for PADDED flag but doesn't + // read/validate the actual pad length byte since that requires reading the payload. + // H2Connection.handleDataFrame() handles this validation when processing DATA frames. @Test void throwsOnPriorityHeadersTooShort() { - assertThrows(H2Exception.class, () -> decode(buildFrame(1, 0x24, 1, new byte[3]))); - } - - @Test - void removePaddingFromPushPromise() throws IOException { - // PUSH_PROMISE (type 5) with PADDED flag (0x08), pad=1, promised stream=2, header block='x' - byte[] payload = {1, 0, 0, 0, 2, 'x', 0}; // padLen=1, promisedId=2, data='x', padding=0 - var codec = new H2FrameCodec(new ByteArrayInputStream(buildFrame(5, 0x0C, 1, payload)), - new ByteArrayOutputStream(), - 16384); - var frame = codec.readFrame(); - - assertEquals(5, frame.type()); - assertEquals(5, frame.payloadLength()); // promisedId(4) + 'x'(1) - byte[] headerBlock = codec.readHeaderBlock(frame); - assertArrayEquals(new byte[] {'x'}, headerBlock); - } - - @Test - void removePriorityFromHeaders() throws IOException { - // HEADERS with PRIORITY flag - 5 bytes priority + header block - byte[] payload = {0, 0, 0, 1, 16, 'a', 'b'}; // dependency=1, weight=16, headers='ab' - var frame = decode(buildFrame(1, 0x24, 1, payload)); // PRIORITY | END_HEADERS - - assertEquals(2, frame.payloadLength()); - assertArrayEquals(new byte[] {'a', 'b'}, Arrays.copyOfRange(frame.payload(), 0, frame.payloadLength())); + assertThrows(H2Exception.class, () -> decodeAndReadPayload(buildFrame(1, 0x24, 1, new byte[3]))); } // validateFrameSize tests @@ -316,11 +368,11 @@ void throwsOnPushPromiseStreamIdZero() { // Frame size exceeds max during read @Test void throwsOnFrameSizeExceedsMax() { - var codec = new H2FrameCodec(new ByteArrayInputStream(buildFrame(0, 0, 1, new byte[200])), - new ByteArrayOutputStream(), + var codec = new H2FrameCodec(wrapIn(buildFrame(0, 0, 1, new byte[200])), + wrapOut(new ByteArrayOutputStream()), 100); - assertThrows(H2Exception.class, codec::readFrame); + assertThrows(H2Exception.class, codec::nextFrame); } // removePadding edge case - empty payload @@ -329,120 +381,129 @@ void throwsOnPaddedEmptyPayload() { assertThrows(H2Exception.class, () -> decode(buildFrame(0, 0x08, 1, new byte[0]))); } - // PUSH_PROMISE payload too short for promised stream ID (in readHeaderBlock) - @Test - void throwsOnPushPromisePayloadTooShortForStreamId() throws IOException { - // Create a Frame directly with short payload to test readHeaderBlock path - var frame = new H2FrameCodec.Frame(5, 0x04, 1, new byte[2], 2); - var codec = new H2FrameCodec(new ByteArrayInputStream(new byte[0]), new ByteArrayOutputStream(), 16384); - - assertThrows(H2Exception.class, () -> codec.readHeaderBlock(frame)); - } - - // Frame.parseSettings edge cases + // PUSH_PROMISE payload too short for promised stream ID (validated at nextFrame() time) @Test - void parseSettingsWrongFrameType() throws IOException { - var frame = decode(buildFrame(0, 0, 1, new byte[1])); // DATA frame - assertThrows(H2Exception.class, frame::parseSettings); + void throwsOnPushPromisePayloadTooShortForStreamId() { + // Build a PUSH_PROMISE frame with payload too short for stream ID (requires 4 bytes min) + var codec = new H2FrameCodec(wrapIn(buildFrame(5, 0x04, 1, new byte[2])), + wrapOut(new ByteArrayOutputStream()), + 16384); + // Validation now happens at nextFrame() time + assertThrows(H2Exception.class, codec::nextFrame); } + // Codec.parseSettings edge cases @Test void parseSettingsPayloadNotMultipleOf6() throws IOException { - var frame = decode(buildFrame(4, 0, 0, new byte[7])); // 7 bytes, not multiple of 6 - - assertThrows(H2Exception.class, frame::parseSettings); - } + var codec = new H2FrameCodec(wrapIn(new byte[0]), wrapOut(new ByteArrayOutputStream()), 16384); + byte[] payload = new byte[7]; // 7 bytes, not multiple of 6 - // Frame.parseGoaway edge cases - @Test - void parseGoawayWrongFrameType() throws IOException { - var frame = decode(buildFrame(0, 0, 1, new byte[1])); - - assertThrows(H2Exception.class, frame::parseGoaway); + assertThrows(H2Exception.class, () -> codec.parseSettings(payload, 7)); } + // Codec.parseGoaway edge cases @Test void parseGoawayPayloadTooShort() { - var frame = new H2FrameCodec.Frame(7, 0, 0, new byte[4], 4); - - assertThrows(H2Exception.class, frame::parseGoaway); - } - - // Frame.parseWindowUpdate edge cases - @Test - void parseWindowUpdateWrongFrameType() throws IOException { - var frame = decode(buildFrame(0, 0, 1, new byte[1])); + var codec = new H2FrameCodec(wrapIn(new byte[0]), wrapOut(new ByteArrayOutputStream()), 16384); - assertThrows(H2Exception.class, frame::parseWindowUpdate); + assertThrows(H2Exception.class, () -> codec.parseGoaway(new byte[4], 4)); } + // Codec.parseWindowUpdate edge cases @Test void parseWindowUpdateWrongPayloadLength() { - // WINDOW_UPDATE frame with 3-byte payload instead of required 4 - var frame = new H2FrameCodec.Frame(8, 0, 1, new byte[3], 3); + var codec = new H2FrameCodec(wrapIn(new byte[0]), wrapOut(new ByteArrayOutputStream()), 16384); - assertThrows(H2Exception.class, frame::parseWindowUpdate); + assertThrows(H2Exception.class, () -> codec.parseWindowUpdate(new byte[3], 3)); } @Test void parseWindowUpdateZeroIncrement() throws IOException { - var frame = decode(buildFrame(8, 0, 1, new byte[4])); // all zeros = increment 0 + var codec = new H2FrameCodec(wrapIn(new byte[0]), wrapOut(new ByteArrayOutputStream()), 16384); - assertThrows(H2Exception.class, frame::parseWindowUpdate); - } - - // Frame.parseRstStream edge cases - @Test - void parseRstStreamWrongFrameType() throws IOException { - var frame = decode(buildFrame(0, 0, 1, new byte[1])); - - assertThrows(H2Exception.class, frame::parseRstStream); + assertThrows(H2Exception.class, () -> codec.parseWindowUpdate(new byte[4], 4)); // all zeros = increment 0 } + // Codec.parseRstStream edge cases @Test void parseRstStreamWrongPayloadLength() throws IOException { - var frame = new H2FrameCodec.Frame(3, 0, 1, new byte[3], 3); + var codec = new H2FrameCodec(wrapIn(new byte[0]), wrapOut(new ByteArrayOutputStream()), 16384); - assertThrows(H2Exception.class, frame::parseRstStream); + assertThrows(H2Exception.class, () -> codec.parseRstStream(new byte[3], 3)); } // Incomplete payload reads @Test - void throwsOnIncompleteControlFramePayload() { + void throwsOnIncompletePayload() { // Build header claiming 8-byte PING payload but only provide 4 bytes byte[] truncated = new byte[9 + 4]; // header + partial payload truncated[2] = 8; // length = 8 truncated[3] = 6; // type = PING // streamId = 0 (already zeros) - var codec = new H2FrameCodec(new ByteArrayInputStream(truncated), new ByteArrayOutputStream(), 16384); + var codec = new H2FrameCodec(wrapIn(truncated), wrapOut(new ByteArrayOutputStream()), 16384); - assertThrows(IOException.class, codec::readFrame); - } - - @Test - void throwsOnIncompleteDataFramePayload() { - // Build header claiming 100-byte DATA payload but only provide 50 bytes - byte[] truncated = new byte[9 + 50]; - truncated[2] = 100; // length = 100 - truncated[3] = 0; // type = DATA - truncated[8] = 1; // streamId = 1 - var codec = new H2FrameCodec(new ByteArrayInputStream(truncated), new ByteArrayOutputStream(), 16384); - - assertThrows(IOException.class, codec::readFrame); + assertThrows(IOException.class, () -> { + codec.nextFrame(); + byte[] payload = new byte[codec.framePayloadLength()]; + codec.readPayloadInto(payload, 0, payload.length); + }); } // Helpers - private H2FrameCodec codec(ByteArrayOutputStream out) { - return new H2FrameCodec(new ByteArrayInputStream(new byte[0]), out, 16384); + private static final int BUF_SIZE = 8192; + + private UnsyncBufferedInputStream wrapIn(byte[] data) { + return new UnsyncBufferedInputStream(new ByteArrayInputStream(data), BUF_SIZE); } - private H2FrameCodec.Frame decode(ByteArrayOutputStream out) throws IOException { - var c = new H2FrameCodec(new ByteArrayInputStream(out.toByteArray()), new ByteArrayOutputStream(), 16384); - return c.readFrame(); + private UnsyncBufferedOutputStream wrapOut(ByteArrayOutputStream out) { + return new UnsyncBufferedOutputStream(out, BUF_SIZE); } - private H2FrameCodec.Frame decode(byte[] frame) throws IOException { - return new H2FrameCodec(new ByteArrayInputStream(frame), new ByteArrayOutputStream(), 16384).readFrame(); + private H2FrameCodec codec(ByteArrayOutputStream out) { + return new H2FrameCodec(wrapIn(new byte[0]), wrapOut(out), 16384); + } + + private TestFrame decode(ByteArrayOutputStream out) throws IOException { + var codec = new H2FrameCodec(wrapIn(out.toByteArray()), wrapOut(new ByteArrayOutputStream()), 16384); + int type = codec.nextFrame(); + int flags = codec.frameFlags(); + int streamId = codec.frameStreamId(); + int length = codec.framePayloadLength(); + byte[] payload; + if (length == 0) { + payload = new byte[0]; + } else { + payload = new byte[length]; + codec.readPayloadInto(payload, 0, length); + } + return new TestFrame(type, flags, streamId, payload, length, codec); + } + + private TestFrame decode(byte[] frame) throws IOException { + var codec = new H2FrameCodec(wrapIn(frame), wrapOut(new ByteArrayOutputStream()), 16384); + int type = codec.nextFrame(); + int flags = codec.frameFlags(); + int streamId = codec.frameStreamId(); + int length = codec.framePayloadLength(); + byte[] payload; + if (length == 0) { + payload = new byte[0]; + } else { + payload = new byte[length]; + codec.readPayloadInto(payload, 0, length); + } + return new TestFrame(type, flags, streamId, payload, length, codec); + } + + private void decodeAndReadPayload(byte[] frame) throws IOException { + var codec = new H2FrameCodec(wrapIn(frame), wrapOut(new ByteArrayOutputStream()), 16384); + int type = codec.nextFrame(); + int length = codec.framePayloadLength(); + if (length > 0) { + byte[] payload = new byte[length]; + codec.readPayloadInto(payload, 0, length); + } } private byte[] buildFrame(int type, int flags, int streamId, byte[] payload) { diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameTestSuiteTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameTestSuiteTest.java index 84618dee4..6f9884d63 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameTestSuiteTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameTestSuiteTest.java @@ -25,6 +25,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.smithy.java.http.client.UnsyncBufferedInputStream; +import software.amazon.smithy.java.http.client.UnsyncBufferedOutputStream; /** * HTTP/2 frame codec test suite using test vectors from http2jp/http2-frame-test-case. @@ -53,6 +55,59 @@ class H2FrameTestSuiteTest { // Codec strips PADDED and PRIORITY flags after processing padding/priority fields private static final int STRIPPED_FLAGS_MASK = ~(FLAG_PADDED | FLAG_PRIORITY); + /** + * Helper record that wraps stateful codec API for test convenience. + */ + record TestFrame(int type, int flags, int streamId, byte[] payload, int length, H2FrameCodec codec) { + boolean hasFlag(int flag) { + return (flags & flag) != 0; + } + + int payloadLength() { + return length; + } + + int[] parseSettings() throws H2Exception { + return codec.parseSettings(payload, length); + } + + int[] parseGoaway() throws H2Exception { + return codec.parseGoaway(payload, length); + } + + int parseWindowUpdate() throws H2Exception { + return codec.parseWindowUpdate(payload, length); + } + + int parseRstStream() throws H2Exception { + return codec.parseRstStream(payload, length); + } + } + + /** + * Read a frame using the stateful API and return a TestFrame for convenience. + */ + private static TestFrame readFrame(H2FrameCodec codec) throws IOException { + int type = codec.nextFrame(); + if (type < 0) { + return null; + } + + int flags = codec.frameFlags(); + int streamId = codec.frameStreamId(); + int payloadLength = codec.framePayloadLength(); + + byte[] payload; + if (payloadLength == 0) { + payload = new byte[0]; + } else { + payload = new byte[payloadLength]; + codec.readPayloadInto(payload, 0, payloadLength); + } + + return new TestFrame(type, flags, streamId, payload, payloadLength, codec); + } + static Stream frameTestCases() throws IOException { List args = new ArrayList<>(); @@ -108,12 +163,12 @@ void decodeFrame( ) throws IOException { byte[] wireBytes = hexToBytes(wireHex); - H2FrameCodec codec = new H2FrameCodec(new ByteArrayInputStream(wireBytes), new ByteArrayOutputStream(), 16384); + H2FrameCodec codec = new H2FrameCodec(wrapIn(wireBytes), wrapOut(new ByteArrayOutputStream()), 16384); if (expectError) { - assertThrows(IOException.class, codec::readFrame, "Expected error for: " + description); + assertThrows(IOException.class, () -> readFrame(codec), "Expected error for: " + description); } else { - H2FrameCodec.Frame frame = codec.readFrame(); + TestFrame frame = readFrame(codec); assertNotNull(frame, "Frame should not be null for: " + description); assertEquals(expectedType, frame.type(), "Type mismatch for: " + description); @@ -150,12 +205,12 @@ static Stream roundTripTestCases() throws IOException { @MethodSource("roundTripTestCases") void roundTripFrame(String description, String wireHex) throws IOException { byte[] wireBytes = hexToBytes(wireHex); - var decodeCodec = new H2FrameCodec(new ByteArrayInputStream(wireBytes), new ByteArrayOutputStream(), 16384); - H2FrameCodec.Frame original = decodeCodec.readFrame(); + var decodeCodec = new H2FrameCodec(wrapIn(wireBytes), wrapOut(new ByteArrayOutputStream()), 16384); + TestFrame original = readFrame(decodeCodec); // Re-encode var encodeOut = new ByteArrayOutputStream(); - var encodeCodec = new H2FrameCodec(new ByteArrayInputStream(new byte[0]), encodeOut, 16384); + var encodeCodec = new H2FrameCodec(wrapIn(new byte[0]), wrapOut(encodeOut), 16384); encodeCodec.writeFrame( original.type(), original.flags(), @@ -166,10 +221,10 @@ void roundTripFrame(String description, String wireHex) throws IOException { encodeCodec.flush(); // Decode again - var redecodeCodec = new H2FrameCodec(new ByteArrayInputStream(encodeOut.toByteArray()), - new ByteArrayOutputStream(), + var redecodeCodec = new H2FrameCodec(wrapIn(encodeOut.toByteArray()), + wrapOut(new ByteArrayOutputStream()), 16384); - H2FrameCodec.Frame roundTripped = redecodeCodec.readFrame(); + TestFrame roundTripped = readFrame(redecodeCodec); // Verify matches assertEquals(original.type(), roundTripped.type(), "Type mismatch after round-trip: " + description); @@ -188,7 +243,7 @@ void roundTripFrame(String description, String wireHex) throws IOException { assertArrayEquals(origPayload, rtPayload, "Payload mismatch after round-trip: " + description); } - private void verifyPayload(H2FrameCodec.Frame frame, JsonNode payload, String description) throws IOException { + private void verifyPayload(TestFrame frame, JsonNode payload, String description) throws IOException { if (payload == null) { return; } @@ -206,15 +261,25 @@ private void verifyPayload(H2FrameCodec.Frame frame, JsonNode payload, String de } } - private void verifyDataPayload(H2FrameCodec.Frame frame, JsonNode payload, String description) { + private void verifyDataPayload(TestFrame frame, JsonNode payload, String description) { if (payload.has("data")) { String expectedData = payload.get("data").asText(); - String actualData = new String(frame.payload(), 0, frame.payloadLength(), StandardCharsets.UTF_8); + + // Handle PADDED frames - the raw payload includes: [padLength][data][padding] + int offset = 0; + int dataLength = frame.payloadLength(); + if (frame.hasFlag(FLAG_PADDED) && payload.has("padding_length")) { + int padLength = payload.get("padding_length").asInt(); + offset = 1; // Skip the pad length byte + dataLength = frame.payloadLength() - 1 - padLength; + } + + String actualData = new String(frame.payload(), offset, dataLength, StandardCharsets.UTF_8); assertEquals(expectedData, actualData, "DATA payload mismatch for: " + description); } } - private void verifyRstStreamPayload(H2FrameCodec.Frame frame, JsonNode payload, String description) + private void verifyRstStreamPayload(TestFrame frame, JsonNode payload, String description) throws IOException { if (payload.has("error_code")) { int expectedErrorCode = payload.get("error_code").asInt(); @@ -223,7 +288,7 @@ private void verifyRstStreamPayload(H2FrameCodec.Frame frame, JsonNode payload, } } - private void verifySettingsPayload(H2FrameCodec.Frame frame, JsonNode payload, String description) + private void verifySettingsPayload(TestFrame frame, JsonNode payload, String description) throws IOException { if (payload.has("settings")) { int[] settings = frame.parseSettings(); @@ -243,7 +308,7 @@ private void verifySettingsPayload(H2FrameCodec.Frame frame, JsonNode payload, S } } - private void verifyPingPayload(H2FrameCodec.Frame frame, JsonNode payload, String description) { + private void verifyPingPayload(TestFrame frame, JsonNode payload, String description) { if (payload.has("opaque_data")) { String expectedStr = payload.get("opaque_data").asText(); byte[] expected = expectedStr.getBytes(StandardCharsets.US_ASCII); @@ -253,7 +318,7 @@ private void verifyPingPayload(H2FrameCodec.Frame frame, JsonNode payload, Strin } } - private void verifyGoawayPayload(H2FrameCodec.Frame frame, JsonNode payload, String description) + private void verifyGoawayPayload(TestFrame frame, JsonNode payload, String description) throws IOException { int[] goaway = frame.parseGoaway(); if (payload.has("last_stream_id")) { @@ -268,7 +333,7 @@ private void verifyGoawayPayload(H2FrameCodec.Frame frame, JsonNode payload, Str } } - private void verifyWindowUpdatePayload(H2FrameCodec.Frame frame, JsonNode payload, String description) + private void verifyWindowUpdatePayload(TestFrame frame, JsonNode payload, String description) throws IOException { if (payload.has("window_size_increment")) { int expected = payload.get("window_size_increment").asInt(); @@ -285,4 +350,14 @@ private static byte[] hexToBytes(String hex) { } return data; } + + private static final int BUF_SIZE = 8192; + + private static UnsyncBufferedInputStream wrapIn(byte[] data) { + return new UnsyncBufferedInputStream(new ByteArrayInputStream(data), BUF_SIZE); + } + + private static UnsyncBufferedOutputStream wrapOut(ByteArrayOutputStream out) { + return new UnsyncBufferedOutputStream(out, BUF_SIZE); + } } diff --git a/io/src/main/java/software/amazon/smithy/java/io/ByteBufferOutputStream.java b/io/src/main/java/software/amazon/smithy/java/io/ByteBufferOutputStream.java index d23de9911..187ff6869 100644 --- a/io/src/main/java/software/amazon/smithy/java/io/ByteBufferOutputStream.java +++ b/io/src/main/java/software/amazon/smithy/java/io/ByteBufferOutputStream.java @@ -68,6 +68,35 @@ public int size() { return count; } + /** + * Returns the internal buffer array. + * + *

      This provides direct access to avoid copying. The valid data is from index 0 to {@link #size()} - 1. + * The buffer may be larger than the valid data. Do not hold onto a reference to this data. + * + * @return the internal buffer + */ + public byte[] array() { + return buf; + } + + /** + * Writes an ASCII string directly to the buffer. + * Each character is cast to a byte (assumes ASCII/Latin-1 input). + * + * @param s the string to write + */ + @SuppressWarnings("deprecation") + public void writeAscii(String s) { + int len = s.length(); + if (len == 0) { + return; + } + ensureCapacity(count + len); + s.getBytes(0, len, buf, count); + count += len; + } + /** * Resets the stream to empty, allowing buffer reuse. * The internal buffer is retained, avoiding reallocation. diff --git a/io/src/main/java/software/amazon/smithy/java/io/datastream/ByteBufferDataStream.java b/io/src/main/java/software/amazon/smithy/java/io/datastream/ByteBufferDataStream.java index 0bd2b9647..7d36d001c 100644 --- a/io/src/main/java/software/amazon/smithy/java/io/datastream/ByteBufferDataStream.java +++ b/io/src/main/java/software/amazon/smithy/java/io/datastream/ByteBufferDataStream.java @@ -43,7 +43,9 @@ public ByteBuffer asByteBuffer() { @Override public InputStream asInputStream() { - return ByteBufferUtils.byteBufferInputStream(buffer); + // Use duplicate() to avoid mutating the original buffer's position, + // allowing the DataStream to be replayed (isReplayable() returns true) + return ByteBufferUtils.byteBufferInputStream(buffer.duplicate()); } @Override From 0c7613c10e5a1ebde59e3817decb35ce4bd2fc65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Sugawara=20=28=E2=88=A9=EF=BD=80-=C2=B4=29?= =?UTF-8?q?=E2=8A=83=E2=94=81=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E?= Date: Tue, 27 Jan 2026 11:30:33 -0800 Subject: [PATCH 36/60] Ran spotlessApply --- .../java/http/client/H2cScalingBenchmark.java | 29 ++++++++++--------- .../java/http/client/BenchmarkServer.java | 2 -- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java index f6ffbf436..d7314ff98 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java @@ -8,6 +8,7 @@ import io.helidon.webclient.api.HttpClientResponse; import io.helidon.webclient.http2.Http2Client; import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; @@ -17,7 +18,6 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.buffer.Unpooled; import io.netty.handler.codec.http2.DefaultHttp2DataFrame; import io.netty.handler.codec.http2.DefaultHttp2Headers; import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; @@ -28,6 +28,16 @@ import io.netty.handler.codec.http2.Http2StreamChannel; import io.netty.handler.codec.http2.Http2StreamChannelBootstrap; import io.netty.handler.codec.http2.Http2StreamFrame; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.openjdk.jmh.annotations.AuxCounters; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -48,17 +58,6 @@ import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; import software.amazon.smithy.java.io.datastream.DataStream; -import java.io.OutputStream; -import java.net.InetSocketAddress; -import java.net.URI; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - /** * HTTP/2 cleartext (h2c) client scaling benchmark. * @@ -477,7 +476,8 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { streamChannel.write(new DefaultHttp2HeadersFrame(h, false)); // Send body with endStream=true streamChannel.writeAndFlush(new DefaultHttp2DataFrame( - Unpooled.wrappedBuffer(BenchmarkSupport.POST_PAYLOAD), true)); + Unpooled.wrappedBuffer(BenchmarkSupport.POST_PAYLOAD), + true)); }); latch.await(); @@ -540,7 +540,8 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { streamChannel.write(new DefaultHttp2HeadersFrame(h, false)); // Send body with endStream=true streamChannel.writeAndFlush(new DefaultHttp2DataFrame( - Unpooled.wrappedBuffer(BenchmarkSupport.MB_PAYLOAD), true)); + Unpooled.wrappedBuffer(BenchmarkSupport.MB_PAYLOAD), + true)); }); latch.await(); diff --git a/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java index babf55813..ea425c00d 100644 --- a/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java +++ b/http/http-client/src/jmhServer/java/software/amazon/smithy/java/http/client/BenchmarkServer.java @@ -17,7 +17,6 @@ import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; @@ -36,7 +35,6 @@ import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; import io.netty.handler.codec.http2.Http2DataFrame; import io.netty.handler.codec.http2.Http2FrameCodecBuilder; -import io.netty.handler.codec.http2.Http2FrameStream; import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.Http2HeadersFrame; import io.netty.handler.codec.http2.Http2MultiplexHandler; From 9a425e87e1ddb06b121a9d7ab8e7ed44d3e79d16 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 29 Jan 2026 16:03:26 -0600 Subject: [PATCH 37/60] Fix H1 pooling, h2 flow control, add integ tests - Add a *bunch* of integration tests and integ test library-ish code - Fix H1 connection pool race condition causing connections to be created instead of reused under high concurrency - Add batched flow control acquisition for H2 to prevent connection window starvation --- .../http/client/it/InterceptorIntegTest.java | 254 ++++++++++++++ .../it/RequestResponseHttp11ClearTest.java | 83 ----- .../it/RequestResponseHttp11WithTlsTest.java | 96 ------ .../it/RequestResponseHttp2ClearTest.java | 86 ----- .../it/RequestResponseHttp2WithAlpnTest.java | 95 ------ .../it/RequestResponseHttp2WithTlsTest.java | 95 ------ .../it/RequestStreamingHttp2ClearTest.java | 88 ----- .../it/RequestStreamingHttp2WithAlpnTest.java | 99 ------ .../it/RequestStreamingHttp2WithTlsTest.java | 99 ------ .../smithy/java/http/client/it/TestUtils.java | 5 +- .../http/client/it/TlsValidationTest.java | 318 ++++++++++++++++++ .../client/it/h1/BaseHttpClientIntegTest.java | 97 ++++++ .../it/h1/ChunkedRequestHttp11Test.java | 57 ++++ .../it/h1/ChunkedResponseHttp11Test.java | 44 +++ .../it/h1/ConnectTimeoutHttp11Test.java | 47 +++ .../it/h1/ConnectionCloseHttp11Test.java | 52 +++ .../ConnectionPoolExhaustionHttp11Test.java | 73 ++++ ...onnectionPoolHighConcurrencyReuseTest.java | 90 +++++ .../it/h1/ConnectionPoolReuseHttp11Test.java | 54 +++ .../http/client/it/h1/ContinueHttp11Test.java | 62 ++++ .../client/it/h1/EmptyBodyHttp11Test.java | 73 ++++ .../it/h1/HighConcurrencyHttp11Test.java | 94 ++++++ .../h1/IdleConnectionCleanupHttp11Test.java | 60 ++++ .../client/it/h1/LargeHeadersHttp11Test.java | 51 +++ .../it/h1/PerRouteLimitsHttp11Test.java | 111 ++++++ .../client/it/h1/ReadTimeoutHttp11Test.java | 47 +++ .../it/h1/RequestResponseHttp11ClearTest.java | 51 +++ .../h1/RequestResponseHttp11WithTlsTest.java | 76 +++++ .../client/it/h1/StatusCodesHttp11Test.java | 124 +++++++ .../it/h1/TrailerHeadersHttp11Test.java | 58 ++++ .../client/it/h2/BaseHttpClientIntegTest.java | 97 ++++++ .../it/h2/ConcurrentStreamsHttp2Test.java | 71 ++++ .../client/it/h2/FlowControlHttp2Test.java | 67 ++++ .../http/client/it/h2/GoawayHttp2Test.java | 55 +++ .../it/h2/HighConcurrencyHttp2Test.java | 91 +++++ .../it/h2/MaxConcurrentStreamsHttp2Test.java | 71 ++++ .../it/h2/RequestResponseHttp2ClearTest.java | 56 +++ .../h2/RequestResponseHttp2WithAlpnTest.java | 81 +++++ .../h2/RequestResponseHttp2WithTlsTest.java | 81 +++++ .../it/h2/RequestStreamingHttp2ClearTest.java | 57 ++++ .../h2/RequestStreamingHttp2WithAlpnTest.java | 81 +++++ .../h2/RequestStreamingHttp2WithTlsTest.java | 81 +++++ .../http/client/it/h2/RstStreamHttp2Test.java | 43 +++ .../it/h2/ServerCloseMidStreamHttp2Test.java | 46 +++ .../it/h2/StreamingResponseHttp2Test.java | 67 ++++ .../client/it/h2/TrailerHeadersHttp2Test.java | 58 ++++ .../client/it/server/Http11ClientHandler.java | 25 -- .../client/it/server/Http2ClientHandler.java | 20 -- .../client/it/server/NettyTestServer.java | 7 + .../client/it/server/ServerInitializer.java | 17 +- .../ChunkedResponseHttp11ClientHandler.java | 44 +++ .../ConnectionCloseHttp11ClientHandler.java | 40 +++ ...ConnectionTrackingHttp11ClientHandler.java | 67 ++++ .../h1/ContinueHttp11ClientHandler.java | 56 +++ .../DelayedResponseHttp11ClientHandler.java | 52 +++ .../h1/EmptyResponseHttp11ClientHandler.java | 27 ++ .../it/server/h1/Http11ClientHandler.java | 60 ++++ .../{ => h1}/Http11ClientHandlerFactory.java | 3 +- .../it/server/{ => h1}/Http11Handler.java | 3 +- .../h1/LargeHeadersHttp11ClientHandler.java | 50 +++ .../MultiplexingHttp11ClientHandler.java | 2 +- .../RequestCapturingHttp11ClientHandler.java | 4 +- .../h1/StatusCodeHttp11ClientHandler.java | 39 +++ .../TextResponseHttp11ClientHandler.java | 23 +- .../TrailerResponseHttp11ClientHandler.java | 52 +++ .../ConnectionTrackingHttp2ClientHandler.java | 54 +++ .../h2/DelayedResponseHttp2ClientHandler.java | 58 ++++ .../h2/GoawayAfterFirstRequestHandler.java | 60 ++++ .../it/server/h2/Http2ClientHandler.java | 42 +++ .../{ => h2}/Http2ClientHandlerFactory.java | 3 +- .../{ => h2}/Http2ConnectionFrameHandler.java | 3 +- .../{ => h2}/Http2StreamFrameHandler.java | 6 +- .../h2/LargeResponseHttp2ClientHandler.java | 58 ++++ .../MultiplexingHttp2ClientHandler.java | 2 +- .../h2/PartialResponseHttp2ClientHandler.java | 35 ++ .../RequestCapturingHttp2ClientHandler.java | 2 +- .../h2/RstStreamHttp2ClientHandler.java | 29 ++ .../StreamingResponseHttp2ClientHandler.java | 48 +++ .../TextResponseHttp2ClientHandler.java | 8 +- .../h2/TrailerResponseHttp2ClientHandler.java | 49 +++ .../java/http/client/H1ScalingBenchmark.java | 19 ++ .../java/http/client/ManagedHttpExchange.java | 5 + .../client/connection/HttpConnectionPool.java | 28 +- .../http/client/h2/FlowControlWindow.java | 38 +++ .../java/http/client/h2/H2Exchange.java | 99 +++--- .../smithy/java/http/client/h2/H2Muxer.java | 33 +- .../java/http/client/h2/H2StreamState.java | 1 + .../java/http/client/h2/PendingWrite.java | 3 +- .../http/client/h2/FlowControlWindowTest.java | 126 +++++++ .../http/client/h2/H2StreamStateTest.java | 131 ++++++++ .../smithy/java/io/ByteBufferUtils.java | 13 + 91 files changed, 4395 insertions(+), 891 deletions(-) create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/InterceptorIntegTest.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithAlpnTest.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2ClearTest.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithAlpnTest.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TlsValidationTest.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/BaseHttpClientIntegTest.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ChunkedRequestHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ChunkedResponseHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectTimeoutHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionCloseHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionPoolExhaustionHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionPoolHighConcurrencyReuseTest.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionPoolReuseHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ContinueHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/EmptyBodyHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/HighConcurrencyHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/IdleConnectionCleanupHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/LargeHeadersHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/PerRouteLimitsHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ReadTimeoutHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11ClearTest.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11WithTlsTest.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/StatusCodesHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/TrailerHeadersHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/BaseHttpClientIntegTest.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/ConcurrentStreamsHttp2Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/FlowControlHttp2Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/GoawayHttp2Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/HighConcurrencyHttp2Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/MaxConcurrentStreamsHttp2Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2ClearTest.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithAlpnTest.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithTlsTest.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2ClearTest.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithAlpnTest.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithTlsTest.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RstStreamHttp2Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/ServerCloseMidStreamHttp2Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/StreamingResponseHttp2Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/TrailerHeadersHttp2Test.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandler.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandler.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ChunkedResponseHttp11ClientHandler.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ConnectionCloseHttp11ClientHandler.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ConnectionTrackingHttp11ClientHandler.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ContinueHttp11ClientHandler.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/DelayedResponseHttp11ClientHandler.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/EmptyResponseHttp11ClientHandler.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/Http11ClientHandler.java rename http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/{ => h1}/Http11ClientHandlerFactory.java (82%) rename http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/{ => h1}/Http11Handler.java (92%) create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/LargeHeadersHttp11ClientHandler.java rename http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/{ => h1}/MultiplexingHttp11ClientHandler.java (96%) rename http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/{ => h1}/RequestCapturingHttp11ClientHandler.java (94%) create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/StatusCodeHttp11ClientHandler.java rename http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/{ => h1}/TextResponseHttp11ClientHandler.java (78%) create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/TrailerResponseHttp11ClientHandler.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/ConnectionTrackingHttp2ClientHandler.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/DelayedResponseHttp2ClientHandler.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/GoawayAfterFirstRequestHandler.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/Http2ClientHandler.java rename http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/{ => h2}/Http2ClientHandlerFactory.java (82%) rename http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/{ => h2}/Http2ConnectionFrameHandler.java (93%) rename http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/{ => h2}/Http2StreamFrameHandler.java (88%) create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/LargeResponseHttp2ClientHandler.java rename http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/{ => h2}/MultiplexingHttp2ClientHandler.java (94%) create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/PartialResponseHttp2ClientHandler.java rename http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/{ => h2}/RequestCapturingHttp2ClientHandler.java (97%) create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/RstStreamHttp2ClientHandler.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/StreamingResponseHttp2ClientHandler.java rename http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/{ => h2}/TextResponseHttp2ClientHandler.java (91%) create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/TrailerResponseHttp2ClientHandler.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/FlowControlWindowTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2StreamStateTest.java diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/InterceptorIntegTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/InterceptorIntegTest.java new file mode 100644 index 000000000..58945eb49 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/InterceptorIntegTest.java @@ -0,0 +1,254 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.context.Context; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.HttpInterceptor; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; +import software.amazon.smithy.java.io.datastream.DataStream; + +/** + * Integration tests for HTTP interceptors. + */ +public class InterceptorIntegTest { + + private static final String RESPONSE_BODY = "Original response"; + private NettyTestServer server; + private HttpClient client; + + @BeforeEach + void setUp() throws Exception { + server = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new TextResponseHttp11ClientHandler(RESPONSE_BODY)) + .build(); + server.start(); + } + + @AfterEach + void tearDown() throws Exception { + if (client != null) + client.close(); + if (server != null) + server.stop(); + } + + private HttpClient.Builder clientBuilder() { + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + return HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .dnsResolver(staticDns) + .build()); + } + + @Test + void beforeRequestInterceptorModifiesRequest() throws Exception { + var interceptor = new HttpInterceptor() { + @Override + public HttpRequest beforeRequest(HttpClient client, HttpRequest request, Context context) { + var headers = HttpHeaders.ofModifiable(); + for (var entry : request.headers()) { + for (var value : entry.getValue()) { + headers.addHeader(entry.getKey(), value); + } + } + headers.addHeader("x-custom-header", "intercepted"); + return request.toBuilder().headers(headers).build(); + } + }; + + client = clientBuilder().addInterceptor(interceptor).build(); + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, + "http://localhost:" + server.getPort(), + ""); + + var response = client.send(request); + + assertEquals(200, response.statusCode()); + } + + @Test + void interceptResponseModifiesResponse() throws Exception { + var interceptor = new HttpInterceptor() { + @Override + public HttpResponse interceptResponse( + HttpClient client, + HttpRequest request, + Context context, + HttpResponse response + ) { + return HttpResponse.builder() + .statusCode(response.statusCode()) + .headers(response.headers()) + .body(DataStream.ofString("Modified by interceptor")) + .build(); + } + }; + + client = clientBuilder().addInterceptor(interceptor).build(); + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, + "http://localhost:" + server.getPort(), + ""); + + var response = client.send(request); + var body = readBody(response); + + assertEquals("Modified by interceptor", body); + } + + @Test + void multipleInterceptorsExecuteInOrder() throws Exception { + var order = new StringBuilder(); + + var first = new HttpInterceptor() { + @Override + public HttpRequest beforeRequest(HttpClient client, HttpRequest request, Context context) { + order.append("1-before,"); + return request; + } + + @Override + public HttpResponse interceptResponse( + HttpClient client, + HttpRequest request, + Context context, + HttpResponse response + ) { + order.append("1-response,"); + return response; + } + }; + + var second = new HttpInterceptor() { + @Override + public HttpRequest beforeRequest(HttpClient client, HttpRequest request, Context context) { + order.append("2-before,"); + return request; + } + + @Override + public HttpResponse interceptResponse( + HttpClient client, + HttpRequest request, + Context context, + HttpResponse response + ) { + order.append("2-response,"); + return response; + } + }; + + client = clientBuilder().addInterceptor(first).addInterceptor(second).build(); + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, + "http://localhost:" + server.getPort(), + ""); + + client.send(request); + + // beforeRequest: forward order, interceptResponse: reverse order + assertEquals("1-before,2-before,2-response,1-response,", order.toString()); + } + + @Test + void preemptRequestSkipsNetworkCall() throws Exception { + var preemptInterceptor = new HttpInterceptor() { + @Override + public HttpResponse preemptRequest(HttpClient client, HttpRequest request, Context context) { + return HttpResponse.builder() + .statusCode(200) + .headers(HttpHeaders.ofModifiable()) + .body(DataStream.ofString("Preempted")) + .build(); + } + }; + + client = clientBuilder() + .addInterceptor(preemptInterceptor) + .build(); + + // Stop server - if network is called, it will fail + server.stop(); + + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, + "http://localhost:" + server.getPort(), + ""); + + // Should succeed because preempt returns response without network call + var response = client.send(request); + + assertEquals("Preempted", readBody(response)); + } + + @Test + void onErrorInterceptorHandlesFailure() throws Exception { + // Stop server to cause connection failure + server.stop(); + + var errorHandled = new AtomicInteger(); + var interceptor = new HttpInterceptor() { + @Override + public HttpResponse onError( + HttpClient client, + HttpRequest request, + Context context, + IOException exception + ) { + errorHandled.incrementAndGet(); + // Return fallback response + return HttpResponse.builder() + .statusCode(503) + .headers(HttpHeaders.ofModifiable()) + .body(DataStream.ofString("Fallback")) + .build(); + } + }; + + client = clientBuilder().addInterceptor(interceptor).build(); + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, + "http://localhost:" + server.getPort(), + ""); + + var response = client.send(request); + + assertEquals(503, response.statusCode()); + assertEquals("Fallback", readBody(response)); + assertEquals(1, errorHandled.get()); + } + + private String readBody(HttpResponse response) { + var buf = response.body().asByteBuffer(); + var bytes = new byte[buf.remaining()]; + buf.get(bytes); + return new String(bytes, StandardCharsets.UTF_8); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java deleted file mode 100644 index 03799aca6..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11ClearTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.net.InetAddress; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.HttpClient; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.dns.DnsResolver; -import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp11ClientHandler; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp11ClientHandler; -import software.amazon.smithy.java.http.client.it.server.TextResponseHttp11ClientHandler; - -public class RequestResponseHttp11ClearTest { - private static final String RESPONSE_CONTENTS = "Response sent from Http11ClearTest"; - private static final String REQUEST_CONTENTS = "Request sent from Http11ClearTest"; - private RequestCapturingHttp11ClientHandler requestCapturingHandler; - private NettyTestServer server; - private HttpClient client; - - @BeforeEach - void setUp() throws Exception { - requestCapturingHandler = new RequestCapturingHttp11ClientHandler(); - var multiplexer = new MultiplexingHttp11ClientHandler(requestCapturingHandler, - new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS)); - server = NettyTestServer.builder() - .httpVersion(HttpVersion.HTTP_1_1) - .http11HandlerFactory((ctx) -> multiplexer) - .build(); - server.start(); - - DnsResolver staticDns = DnsResolver.staticMapping(Map.of( - "localhost", - List.of(InetAddress.getLoopbackAddress()))); - client = HttpClient.builder() - .connectionPool(HttpConnectionPool.builder() - .maxConnectionsPerRoute(10) - .maxTotalConnections(10) - .maxIdleTime(Duration.ofMinutes(1)) - .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) - .dnsResolver(staticDns) - .build()) - .build(); - } - - @AfterEach - void tearDown() throws Exception { - server.stop(); - client.close(); - } - - @Test - void canSendRequestAndReadResponse() throws Exception { - // -- Arrange - var request = TestUtils.plainTextHttp11Request("http://localhost:" + server.getPort(), REQUEST_CONTENTS); - - // -- Act - var response = client.send(request); - var bodyByteBuf = response.body().asByteBuffer(); - var bytes = new byte[bodyByteBuf.remaining()]; - bodyByteBuf.get(bytes); - var responseBody = new String(bytes, StandardCharsets.UTF_8); - - // -- Assert - var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); - assertEquals(REQUEST_CONTENTS, capturedRequestBody); - assertEquals(RESPONSE_CONTENTS, responseBody); - } -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java deleted file mode 100644 index f056b1e2f..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp11WithTlsTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static software.amazon.smithy.java.http.client.it.TestUtils.createClientSslContext; -import static software.amazon.smithy.java.http.client.it.TestUtils.createServerSslContextBuilder; - -import java.net.InetAddress; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.HttpClient; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.dns.DnsResolver; -import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp11ClientHandler; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp11ClientHandler; -import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; -import software.amazon.smithy.java.http.client.it.server.TextResponseHttp11ClientHandler; - -public class RequestResponseHttp11WithTlsTest { - private static final String RESPONSE_CONTENTS = "Response sent from Http2WithTls"; - private static final String REQUEST_CONTENTS = "Request sent from Http2WithTls"; - private static TestCertificateGenerator.CertificateBundle bundle; - private NettyTestServer server; - private RequestCapturingHttp11ClientHandler requestCapturingHandler; - private HttpClient client; - - @BeforeAll - static void beforeAll() throws Exception { - bundle = TestCertificateGenerator.generateCertificates(); - } - - @BeforeEach - void setUp() throws Exception { - requestCapturingHandler = new RequestCapturingHttp11ClientHandler(); - var multiplexer = new MultiplexingHttp11ClientHandler(requestCapturingHandler, - new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS)); - server = NettyTestServer.builder() - .httpVersion(HttpVersion.HTTP_1_1) - .http11HandlerFactory((ctx) -> multiplexer) - .sslContextBuilder(createServerSslContextBuilder(bundle)) - .build(); - server.start(); - - DnsResolver staticDns = DnsResolver.staticMapping(Map.of( - "localhost", - List.of(InetAddress.getLoopbackAddress()))); - client = HttpClient.builder() - .connectionPool(HttpConnectionPool.builder() - .maxConnectionsPerRoute(10) - .maxTotalConnections(10) - .maxIdleTime(Duration.ofMinutes(1)) - .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) - .sslContext(createClientSslContext(bundle)) - .dnsResolver(staticDns) - .build()) - .build(); - - } - - @AfterEach - void tearDown() { - server.stop(); - } - - @Test - public void canSendRequestAndReadResponse() throws Exception { - // -- Arrange - var request = TestUtils.plainTextHttp2Request("https://localhost:" + server.getPort(), REQUEST_CONTENTS); - - // -- Act - var response = client.send(request); - var bodyByteBuf = response.body().asByteBuffer(); - var bytes = new byte[bodyByteBuf.remaining()]; - bodyByteBuf.get(bytes); - var responseBody = new String(bytes, StandardCharsets.UTF_8); - client.close(); - - // -- Assert - var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); - assertEquals(REQUEST_CONTENTS, capturedRequestBody); - assertEquals(RESPONSE_CONTENTS, responseBody); - } -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java deleted file mode 100644 index ea40299f6..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2ClearTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.net.InetAddress; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.HttpClient; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.dns.DnsResolver; -import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.TextResponseHttp2ClientHandler; - -public class RequestResponseHttp2ClearTest { - private static final String RESPONSE_CONTENTS = "Response sent from Http2ClearTest"; - private static final String REQUEST_CONTENTS = "Request sent from Http2ClearTest"; - private RequestCapturingHttp2ClientHandler requestCapturingHandler; - private NettyTestServer server; - private HttpClient client; - - @BeforeEach - void setUp() throws Exception { - requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); - var multiplexer = new MultiplexingHttp2ClientHandler(requestCapturingHandler, - new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS)); - server = NettyTestServer.builder() - .httpVersion(HttpVersion.HTTP_2) - .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) - .http2HandlerFactory((ctx) -> multiplexer) - // fixed port to wireshark the traffic - .port(8080) - .build(); - server.start(); - - DnsResolver staticDns = DnsResolver.staticMapping(Map.of( - "localhost", - List.of(InetAddress.getLoopbackAddress()))); - client = HttpClient.builder() - .connectionPool(HttpConnectionPool.builder() - .maxConnectionsPerRoute(10) - .maxTotalConnections(10) - .maxIdleTime(Duration.ofMinutes(1)) - .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) - .dnsResolver(staticDns) - .build()) - .build(); - } - - @AfterEach - void tearDown() { - server.stop(); - } - - @Test - void canSendRequestAndReadResponse() throws Exception { - // -- Arrange - var request = TestUtils.plainTextHttp2Request("http://localhost:" + server.getPort(), REQUEST_CONTENTS); - - // -- Act - var response = client.send(request); - var bodyByteBuf = response.body().asByteBuffer(); - var bytes = new byte[bodyByteBuf.remaining()]; - bodyByteBuf.get(bytes); - var responseBody = new String(bytes, StandardCharsets.UTF_8); - client.close(); - - // -- Assert - var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); - assertEquals(REQUEST_CONTENTS, capturedRequestBody); - assertEquals(RESPONSE_CONTENTS, responseBody); - } -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithAlpnTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithAlpnTest.java deleted file mode 100644 index 3bd3a3dfb..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithAlpnTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static software.amazon.smithy.java.http.client.it.TestUtils.createServerSslContextBuilder; - -import java.net.InetAddress; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.HttpClient; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.dns.DnsResolver; -import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; -import software.amazon.smithy.java.http.client.it.server.TextResponseHttp2ClientHandler; - -public class RequestResponseHttp2WithAlpnTest { - private static final String RESPONSE_CONTENTS = "Response sent from Http2WithTls"; - private static final String REQUEST_CONTENTS = "Request sent from Http2WithTls"; - private static TestCertificateGenerator.CertificateBundle bundle; - private NettyTestServer server; - private RequestCapturingHttp2ClientHandler requestCapturingHandler; - private HttpClient client; - - @BeforeAll - static void beforeAll() throws Exception { - bundle = TestCertificateGenerator.generateCertificates(); - } - - @BeforeEach - void setUp() throws Exception { - requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); - var multiplexer = new MultiplexingHttp2ClientHandler(requestCapturingHandler, - new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS)); - server = NettyTestServer.builder() - .httpVersion(HttpVersion.HTTP_2) - .h2ConnectionMode(NettyTestServer.H2ConnectionMode.ALPN) - .http2HandlerFactory((ctx) -> multiplexer) - .sslContextBuilder(createServerSslContextBuilder(bundle)) - .build(); - server.start(); - - DnsResolver staticDns = DnsResolver.staticMapping(Map.of( - "localhost", - List.of(InetAddress.getLoopbackAddress()))); - client = HttpClient.builder() - .connectionPool(HttpConnectionPool.builder() - .maxConnectionsPerRoute(10) - .maxTotalConnections(10) - .maxIdleTime(Duration.ofMinutes(1)) - .httpVersionPolicy(HttpVersionPolicy.AUTOMATIC) - .sslContext(TestUtils.createClientSslContext(bundle)) - .dnsResolver(staticDns) - .build()) - .build(); - } - - @AfterEach - void tearDown() { - server.stop(); - } - - @Test - public void canSendRequestAndReadResponse() throws Exception { - // -- Arrange - var request = TestUtils.plainTextHttp2Request("https://localhost:" + server.getPort(), REQUEST_CONTENTS); - - // -- Act - var response = client.send(request); - var bodyByteBuf = response.body().asByteBuffer(); - var bytes = new byte[bodyByteBuf.remaining()]; - bodyByteBuf.get(bytes); - var responseBody = new String(bytes, StandardCharsets.UTF_8); - client.close(); - - // -- Assert - var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); - assertEquals(REQUEST_CONTENTS, capturedRequestBody); - assertEquals(RESPONSE_CONTENTS, responseBody); - } -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java deleted file mode 100644 index e4deeb116..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseHttp2WithTlsTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static software.amazon.smithy.java.http.client.it.TestUtils.createServerSslContextBuilder; - -import java.net.InetAddress; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.HttpClient; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.dns.DnsResolver; -import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; -import software.amazon.smithy.java.http.client.it.server.TextResponseHttp2ClientHandler; - -public class RequestResponseHttp2WithTlsTest { - private static final String RESPONSE_CONTENTS = "Response sent from Http2WithTls"; - private static final String REQUEST_CONTENTS = "Request sent from Http2WithTls"; - private static TestCertificateGenerator.CertificateBundle bundle; - private NettyTestServer server; - private RequestCapturingHttp2ClientHandler requestCapturingHandler; - private HttpClient client; - - @BeforeAll - static void beforeAll() throws Exception { - bundle = TestCertificateGenerator.generateCertificates(); - } - - @BeforeEach - void setUp() throws Exception { - requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); - var multiplexer = new MultiplexingHttp2ClientHandler(requestCapturingHandler, - new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS)); - server = NettyTestServer.builder() - .httpVersion(HttpVersion.HTTP_2) - .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) - .http2HandlerFactory((ctx) -> multiplexer) - .sslContextBuilder(createServerSslContextBuilder(bundle)) - .build(); - server.start(); - - DnsResolver staticDns = DnsResolver.staticMapping(Map.of( - "localhost", - List.of(InetAddress.getLoopbackAddress()))); - client = HttpClient.builder() - .connectionPool(HttpConnectionPool.builder() - .maxConnectionsPerRoute(10) - .maxTotalConnections(10) - .maxIdleTime(Duration.ofMinutes(1)) - .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_2) - .sslContext(TestUtils.createClientSslContext(bundle)) - .dnsResolver(staticDns) - .build()) - .build(); - } - - @AfterEach - void tearDown() { - server.stop(); - } - - @Test - public void canSendRequestAndReadResponse() throws Exception { - // -- Arrange - var request = TestUtils.plainTextHttp2Request("https://localhost:" + server.getPort(), REQUEST_CONTENTS); - - // -- Act - var response = client.send(request); - var bodyByteBuf = response.body().asByteBuffer(); - var bytes = new byte[bodyByteBuf.remaining()]; - bodyByteBuf.get(bytes); - var responseBody = new String(bytes, StandardCharsets.UTF_8); - client.close(); - - // -- Assert - var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); - assertEquals(REQUEST_CONTENTS, capturedRequestBody); - assertEquals(RESPONSE_CONTENTS, responseBody); - } -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2ClearTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2ClearTest.java deleted file mode 100644 index 5ab2e6baf..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2ClearTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static software.amazon.smithy.java.http.client.it.TestUtils.IPSUM_LOREM; -import static software.amazon.smithy.java.http.client.it.TestUtils.streamingBody; - -import java.net.InetAddress; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.HttpClient; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.dns.DnsResolver; -import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.TextResponseHttp2ClientHandler; - -public class RequestStreamingHttp2ClearTest { - private static final String RESPONSE_CONTENTS = "Response sent from Http2ClearTest"; - private RequestCapturingHttp2ClientHandler requestCapturingHandler; - private NettyTestServer server; - private HttpClient client; - - @BeforeEach - void setUp() throws Exception { - requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); - var multiplexer = new MultiplexingHttp2ClientHandler(requestCapturingHandler, - new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS)); - server = NettyTestServer.builder() - .httpVersion(HttpVersion.HTTP_2) - .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) - .http2HandlerFactory((ctx) -> multiplexer) - .build(); - server.start(); - - DnsResolver staticDns = DnsResolver.staticMapping(Map.of( - "localhost", - List.of(InetAddress.getLoopbackAddress()))); - client = HttpClient.builder() - .connectionPool(HttpConnectionPool.builder() - .maxConnectionsPerRoute(10) - .maxTotalConnections(10) - .maxIdleTime(Duration.ofMinutes(1)) - .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) - .dnsResolver(staticDns) - .build()) - .build(); - } - - @AfterEach - void tearDown() { - server.stop(); - } - - @Test - void canSendRequestAndReadResponse() throws Exception { - // -- Arrange - var request = TestUtils.request(HttpVersion.HTTP_2, - "http://localhost:" + server.getPort(), - streamingBody(IPSUM_LOREM)); - - // -- Act - var response = client.send(request); - requestCapturingHandler.streamCompleted().join(); - var bodyByteBuf = response.body().asByteBuffer(); - var bytes = new byte[bodyByteBuf.remaining()]; - bodyByteBuf.get(bytes); - var responseBody = new String(bytes, StandardCharsets.UTF_8); - client.close(); - - // -- Assert - var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); - assertEquals(String.join("", IPSUM_LOREM), capturedRequestBody); - assertEquals(RESPONSE_CONTENTS, responseBody); - } -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithAlpnTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithAlpnTest.java deleted file mode 100644 index b0e0f2b5f..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithAlpnTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static software.amazon.smithy.java.http.client.it.TestUtils.IPSUM_LOREM; -import static software.amazon.smithy.java.http.client.it.TestUtils.createServerSslContextBuilder; -import static software.amazon.smithy.java.http.client.it.TestUtils.streamingBody; - -import java.net.InetAddress; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.HttpClient; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.dns.DnsResolver; -import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; -import software.amazon.smithy.java.http.client.it.server.TextResponseHttp2ClientHandler; - -public class RequestStreamingHttp2WithAlpnTest { - private static final String RESPONSE_CONTENTS = "Response sent from Http2ClearTest"; - private static TestCertificateGenerator.CertificateBundle bundle; - private RequestCapturingHttp2ClientHandler requestCapturingHandler; - private NettyTestServer server; - private HttpClient client; - - @BeforeAll - static void beforeAll() throws Exception { - bundle = TestCertificateGenerator.generateCertificates(); - } - - @BeforeEach - void setUp() throws Exception { - requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); - var multiplexer = new MultiplexingHttp2ClientHandler(requestCapturingHandler, - new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS)); - server = NettyTestServer.builder() - .httpVersion(HttpVersion.HTTP_2) - .h2ConnectionMode(NettyTestServer.H2ConnectionMode.ALPN) - .http2HandlerFactory((ctx) -> multiplexer) - .sslContextBuilder(createServerSslContextBuilder(bundle)) - .build(); - server.start(); - - DnsResolver staticDns = DnsResolver.staticMapping(Map.of( - "localhost", - List.of(InetAddress.getLoopbackAddress()))); - client = HttpClient.builder() - .connectionPool(HttpConnectionPool.builder() - .maxConnectionsPerRoute(10) - .maxTotalConnections(10) - .maxIdleTime(Duration.ofMinutes(1)) - .httpVersionPolicy(HttpVersionPolicy.AUTOMATIC) - .sslContext(TestUtils.createClientSslContext(bundle)) - .dnsResolver(staticDns) - .build()) - .build(); - } - - @AfterEach - void tearDown() { - server.stop(); - } - - @Test - void canSendRequestAndReadResponse() throws Exception { - // -- Arrange - var request = TestUtils.request(HttpVersion.HTTP_2, - "https://localhost:" + server.getPort(), - streamingBody(IPSUM_LOREM)); - - // -- Act - var response = client.send(request); - requestCapturingHandler.streamCompleted().join(); - var bodyByteBuf = response.body().asByteBuffer(); - var bytes = new byte[bodyByteBuf.remaining()]; - bodyByteBuf.get(bytes); - var responseBody = new String(bytes, StandardCharsets.UTF_8); - client.close(); - - // -- Assert - var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); - assertEquals(String.join("", IPSUM_LOREM), capturedRequestBody); - assertEquals(RESPONSE_CONTENTS, responseBody); - } -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java deleted file mode 100644 index 29f50c133..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingHttp2WithTlsTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static software.amazon.smithy.java.http.client.it.TestUtils.IPSUM_LOREM; -import static software.amazon.smithy.java.http.client.it.TestUtils.createServerSslContextBuilder; -import static software.amazon.smithy.java.http.client.it.TestUtils.streamingBody; - -import java.net.InetAddress; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.HttpClient; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.dns.DnsResolver; -import software.amazon.smithy.java.http.client.it.server.MultiplexingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.RequestCapturingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; -import software.amazon.smithy.java.http.client.it.server.TextResponseHttp2ClientHandler; - -public class RequestStreamingHttp2WithTlsTest { - private static final String RESPONSE_CONTENTS = "Response sent from Http2ClearTest"; - private static TestCertificateGenerator.CertificateBundle bundle; - private RequestCapturingHttp2ClientHandler requestCapturingHandler; - private NettyTestServer server; - private HttpClient client; - - @BeforeAll - static void beforeAll() throws Exception { - bundle = TestCertificateGenerator.generateCertificates(); - } - - @BeforeEach - void setUp() throws Exception { - requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); - var multiplexer = new MultiplexingHttp2ClientHandler(requestCapturingHandler, - new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS)); - server = NettyTestServer.builder() - .httpVersion(HttpVersion.HTTP_2) - .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) - .http2HandlerFactory((ctx) -> multiplexer) - .sslContextBuilder(createServerSslContextBuilder(bundle)) - .build(); - server.start(); - - DnsResolver staticDns = DnsResolver.staticMapping(Map.of( - "localhost", - List.of(InetAddress.getLoopbackAddress()))); - client = HttpClient.builder() - .connectionPool(HttpConnectionPool.builder() - .maxConnectionsPerRoute(10) - .maxTotalConnections(10) - .maxIdleTime(Duration.ofMinutes(1)) - .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_2) - .sslContext(TestUtils.createClientSslContext(bundle)) - .dnsResolver(staticDns) - .build()) - .build(); - } - - @AfterEach - void tearDown() { - server.stop(); - } - - @Test - void canSendRequestAndReadResponse() throws Exception { - // -- Arrange - var request = TestUtils.request(HttpVersion.HTTP_2, - "https://localhost:" + server.getPort(), - streamingBody(IPSUM_LOREM)); - - // -- Act - var response = client.send(request); - requestCapturingHandler.streamCompleted().join(); - var bodyByteBuf = response.body().asByteBuffer(); - var bytes = new byte[bodyByteBuf.remaining()]; - bodyByteBuf.get(bytes); - var responseBody = new String(bytes, StandardCharsets.UTF_8); - client.close(); - - // -- Assert - var capturedRequestBody = requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8); - assertEquals(String.join("", IPSUM_LOREM), capturedRequestBody); - assertEquals(RESPONSE_CONTENTS, responseBody); - } -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TestUtils.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TestUtils.java index 013f1ee75..517bdc1aa 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TestUtils.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TestUtils.java @@ -28,7 +28,7 @@ import software.amazon.smithy.java.io.datastream.DataStream; public class TestUtils { - static final List IPSUM_LOREM = getIpsumLorem(); + public static final List IPSUM_LOREM = getIpsumLorem(); private TestUtils() {} @@ -54,6 +54,9 @@ public static HttpRequest request(HttpVersion version, String uri, DataStream bo headers.addHeader("content-type", "text/plain"); if (body.contentLength() >= 0) { headers.addHeader("content-length", Long.toString(body.contentLength())); + } else if (version == HttpVersion.HTTP_1_1) { + // HTTP/1.1 needs transfer-encoding for streaming bodies + headers.addHeader("transfer-encoding", "chunked"); } return HttpRequest.builder() .httpVersion(version) diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TlsValidationTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TlsValidationTest.java new file mode 100644 index 000000000..03086ae4b --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TlsValidationTest.java @@ -0,0 +1,318 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import io.netty.handler.ssl.SslContextBuilder; +import java.io.IOException; +import java.math.BigInteger; +import java.net.InetAddress; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.Date; +import java.util.List; +import java.util.Map; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.TrustManagerFactory; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; +import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; + +/** + * Tests TLS certificate validation edge cases. + */ +public class TlsValidationTest { + + private static TestCertificateGenerator.CertificateBundle validBundle; + private NettyTestServer server; + private HttpClient client; + + @BeforeAll + static void beforeAll() throws Exception { + validBundle = TestCertificateGenerator.generateCertificates(); + } + + @AfterEach + void tearDown() throws Exception { + if (client != null) { + client.close(); + } + if (server != null) { + server.stop(); + } + } + + @Test + void rejectsUntrustedCertificate() throws Exception { + // Server uses valid cert, but client doesn't trust the CA + server = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new TextResponseHttp11ClientHandler("response")) + .sslContextBuilder(SslContextBuilder.forServer( + validBundle.serverPrivateKey, + validBundle.serverCertificate)) + .build(); + server.start(); + + // Client with empty trust store (doesn't trust any CA) + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, null, new SecureRandom()); // Default trust manager won't trust test CA + + client = createClient(sslContext); + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, + "https://localhost:" + server.getPort(), + ""); + + var ex = assertThrows(IOException.class, () -> client.send(request)); + assertTlsFailure(ex); + } + + @Test + void rejectsWrongHostname() throws Exception { + // Generate cert for "wronghost.example.com" instead of "localhost" + var wrongHostBundle = generateCertForHost("wronghost.example.com"); + + server = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new TextResponseHttp11ClientHandler("response")) + .sslContextBuilder(SslContextBuilder.forServer( + wrongHostBundle.serverPrivateKey, + wrongHostBundle.serverCertificate)) + .build(); + server.start(); + + // Client trusts the CA but hostname won't match + client = createClient(createTrustingSslContext(wrongHostBundle.caCertificate)); + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, + "https://localhost:" + server.getPort(), + ""); + + var ex = assertThrows(IOException.class, () -> client.send(request)); + assertTlsFailure(ex); + } + + @Test + void rejectsExpiredCertificate() throws Exception { + // Generate expired cert + var expiredBundle = generateExpiredCert(); + + server = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new TextResponseHttp11ClientHandler("response")) + .sslContextBuilder(SslContextBuilder.forServer( + expiredBundle.serverPrivateKey, + expiredBundle.serverCertificate)) + .build(); + server.start(); + + client = createClient(createTrustingSslContext(expiredBundle.caCertificate)); + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, + "https://localhost:" + server.getPort(), + ""); + + var ex = assertThrows(IOException.class, () -> client.send(request)); + assertTlsFailure(ex); + } + + @Test + void rejectsSelfSignedCertificate() throws Exception { + // Generate self-signed cert (no CA) + var selfSignedCert = generateSelfSignedCert(); + + server = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new TextResponseHttp11ClientHandler("response")) + .sslContextBuilder(SslContextBuilder.forServer( + selfSignedCert.privateKey, + selfSignedCert.certificate)) + .build(); + server.start(); + + // Client with default trust store (won't trust self-signed) + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, null, new SecureRandom()); + + client = createClient(sslContext); + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, + "https://localhost:" + server.getPort(), + ""); + + var ex = assertThrows(IOException.class, () -> client.send(request)); + assertTlsFailure(ex); + } + + private void assertTlsFailure(Throwable ex) { + // TLS failures can manifest as SSLHandshakeException or SocketException (socket closed by peer) + // depending on timing of when the failure is detected + Throwable current = ex; + while (current != null) { + if (current instanceof SSLHandshakeException + || (current instanceof java.net.SocketException + && current.getMessage() != null + && current.getMessage().contains("closed"))) { + return; + } + current = current.getCause(); + } + throw new AssertionError("Expected TLS failure (SSLHandshakeException or SocketException) but not found in: " + + ex, ex); + } + + private HttpClient createClient(SSLContext sslContext) { + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + return HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .dnsResolver(staticDns) + .sslContext(sslContext) + .build()) + .build(); + } + + private SSLContext createTrustingSslContext(X509Certificate caCert) throws Exception { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); + keyStore.setCertificateEntry("ca-cert", caCert); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(keyStore); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, tmf.getTrustManagers(), new SecureRandom()); + return sslContext; + } + + private TestCertificateGenerator.CertificateBundle generateCertForHost(String hostname) throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + + KeyPair caKeyPair = keyGen.generateKeyPair(); + X509Certificate caCert = generateCA(caKeyPair); + + KeyPair serverKeyPair = keyGen.generateKeyPair(); + X509Certificate serverCert = generateServerCert(serverKeyPair, + caKeyPair, + caCert, + hostname, + new Date(), + new Date(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000)); + + return new TestCertificateGenerator.CertificateBundle(caCert, serverCert, serverKeyPair.getPrivate()); + } + + private TestCertificateGenerator.CertificateBundle generateExpiredCert() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + + KeyPair caKeyPair = keyGen.generateKeyPair(); + X509Certificate caCert = generateCA(caKeyPair); + + KeyPair serverKeyPair = keyGen.generateKeyPair(); + // Expired: notBefore and notAfter both in the past + Date notBefore = new Date(System.currentTimeMillis() - 2 * 24 * 60 * 60 * 1000); + Date notAfter = new Date(System.currentTimeMillis() - 1 * 24 * 60 * 60 * 1000); + X509Certificate serverCert = generateServerCert(serverKeyPair, + caKeyPair, + caCert, + "localhost", + notBefore, + notAfter); + + return new TestCertificateGenerator.CertificateBundle(caCert, serverCert, serverKeyPair.getPrivate()); + } + + private SelfSignedCert generateSelfSignedCert() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + KeyPair keyPair = keyGen.generateKeyPair(); + + var subject = new X500Name("CN=localhost, O=Test, C=US"); + var certBuilder = new JcaX509v3CertificateBuilder( + subject, + BigInteger.valueOf(System.currentTimeMillis()), + new Date(), + new Date(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000), + subject, + keyPair.getPublic()); + + var signer = new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate()); + var cert = new JcaX509CertificateConverter().getCertificate(certBuilder.build(signer)); + + return new SelfSignedCert(cert, keyPair.getPrivate()); + } + + private X509Certificate generateCA(KeyPair keyPair) throws Exception { + var issuer = new X500Name("CN=Test CA, O=Test, C=US"); + var certBuilder = new JcaX509v3CertificateBuilder( + issuer, + BigInteger.valueOf(System.currentTimeMillis()), + new Date(), + new Date(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000), + issuer, + keyPair.getPublic()) + .addExtension(Extension.basicConstraints, true, new BasicConstraints(true)) + .addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign)); + + var signer = new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate()); + return new JcaX509CertificateConverter().getCertificate(certBuilder.build(signer)); + } + + private X509Certificate generateServerCert( + KeyPair serverKeyPair, + KeyPair caKeyPair, + X509Certificate caCert, + String hostname, + Date notBefore, + Date notAfter + ) throws Exception { + var issuer = X500Name.getInstance(caCert.getSubjectX500Principal().getEncoded()); + var subject = new X500Name("CN=" + hostname + ", O=Test, C=US"); + + var sanNames = new GeneralName[] {new GeneralName(GeneralName.dNSName, hostname)}; + + var certBuilder = new JcaX509v3CertificateBuilder( + issuer, + BigInteger.valueOf(System.currentTimeMillis()), + notBefore, + notAfter, + subject, + serverKeyPair.getPublic()) + .addExtension(Extension.subjectAlternativeName, false, new GeneralNames(sanNames)); + + var signer = new JcaContentSignerBuilder("SHA256withRSA").build(caKeyPair.getPrivate()); + return new JcaX509CertificateConverter().getCertificate(certBuilder.build(signer)); + } + + private record SelfSignedCert(X509Certificate certificate, java.security.PrivateKey privateKey) {} +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/BaseHttpClientIntegTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/BaseHttpClientIntegTest.java new file mode 100644 index 000000000..6606c6235 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/BaseHttpClientIntegTest.java @@ -0,0 +1,97 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import java.io.IOException; +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.TestUtils; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; + +/** + * Base class for HTTP client integration tests. + * Provides common setup/teardown and utility methods. + */ +public abstract class BaseHttpClientIntegTest { + + protected static final String RESPONSE_CONTENTS = "Test response body"; + protected static final String REQUEST_CONTENTS = "Test request body"; + + protected NettyTestServer server; + protected HttpClient client; + + /** + * Configure the test server. + */ + protected abstract NettyTestServer.Builder configureServer(NettyTestServer.Builder builder); + + /** + * Configure the connection pool. + */ + protected abstract HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder); + + @BeforeEach + void setUp() throws Exception { + server = configureServer(NettyTestServer.builder()).build(); + server.start(); + + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + + var poolBuilder = HttpConnectionPool.builder() + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .dnsResolver(staticDns); + + poolBuilder = configurePool(poolBuilder); + + client = HttpClient.builder() + .connectionPool(poolBuilder.build()) + .build(); + } + + @AfterEach + void tearDown() throws Exception { + if (client != null) { + client.close(); + } + if (server != null) { + server.stop(); + } + } + + protected String uri(String path) { + return "http://localhost:" + server.getPort() + path; + } + + protected String uri() { + return uri(""); + } + + protected HttpRequest plainTextRequest(HttpVersion version, String body) { + return TestUtils.plainTextRequest(version, uri(), body); + } + + protected String readBody(HttpResponse response) throws IOException { + try (var body = response.body().asInputStream()) { + return new String(body.readAllBytes(), StandardCharsets.UTF_8); + } + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ChunkedRequestHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ChunkedRequestHttp11Test.java new file mode 100644 index 000000000..203827ec9 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ChunkedRequestHttp11Test.java @@ -0,0 +1,57 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.smithy.java.http.client.it.TestUtils.IPSUM_LOREM; +import static software.amazon.smithy.java.http.client.it.TestUtils.streamingBody; + +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.TestUtils; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.MultiplexingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.RequestCapturingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; + +/** + * Tests HTTP/1.1 chunked transfer encoding for request body. + */ +public class ChunkedRequestHttp11Test extends BaseHttpClientIntegTest { + + private RequestCapturingHttp11ClientHandler requestCapturingHandler; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + requestCapturingHandler = new RequestCapturingHttp11ClientHandler(); + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new MultiplexingHttp11ClientHandler( + requestCapturingHandler, + new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS))); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1); + } + + @Test + void canSendChunkedRequestBody() throws Exception { + // streamingBody has unknown content length (-1), so it will use chunked encoding + var request = TestUtils.request(HttpVersion.HTTP_1_1, uri(), streamingBody(IPSUM_LOREM)); + + var response = client.send(request); + var responseBody = readBody(response); + + assertEquals(String.join("", IPSUM_LOREM), + requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); + assertEquals(RESPONSE_CONTENTS, responseBody); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ChunkedResponseHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ChunkedResponseHttp11Test.java new file mode 100644 index 000000000..27e2b9403 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ChunkedResponseHttp11Test.java @@ -0,0 +1,44 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.ChunkedResponseHttp11ClientHandler; + +/** + * Tests HTTP/1.1 chunked transfer encoding response. + */ +public class ChunkedResponseHttp11Test extends BaseHttpClientIntegTest { + + private static final List CHUNKS = List.of("Hello ", "chunked ", "world!"); + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new ChunkedResponseHttp11ClientHandler(CHUNKS)); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1); + } + + @Test + void readsChunkedTransferEncodingResponse() throws Exception { + var request = plainTextRequest(HttpVersion.HTTP_1_1, ""); + var response = client.send(request); + + assertEquals(String.join("", CHUNKS), readBody(response)); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectTimeoutHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectTimeoutHttp11Test.java new file mode 100644 index 000000000..f8411c77b --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectTimeoutHttp11Test.java @@ -0,0 +1,47 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.time.Duration; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.TestUtils; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; + +/** + * Tests that connection timeout throws SocketTimeoutException. + */ +public class ConnectTimeoutHttp11Test extends BaseHttpClientIntegTest { + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + // Server exists but we'll connect to a non-routable IP + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS)); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .connectTimeout(Duration.ofMillis(100)); // Very short timeout + } + + @Test + void connectTimeoutThrowsException() { + // Use non-routable IP address (RFC 5737 TEST-NET-1) to trigger connect timeout + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, "http://192.0.2.1:12345", ""); + + assertThrows(IOException.class, () -> client.send(request)); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionCloseHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionCloseHttp11Test.java new file mode 100644 index 000000000..8e3a7e189 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionCloseHttp11Test.java @@ -0,0 +1,52 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.ConnectionCloseHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.ConnectionTrackingHttp11ClientHandler; + +/** + * Tests that Connection: close header prevents connection reuse. + */ +public class ConnectionCloseHttp11Test extends BaseHttpClientIntegTest { + + private ConnectionTrackingHttp11ClientHandler trackingHandler; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + trackingHandler = new ConnectionTrackingHttp11ClientHandler( + new ConnectionCloseHttp11ClientHandler(RESPONSE_CONTENTS)); + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> trackingHandler); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1); + } + + @Test + void connectionNotReusedWhenServerSendsClose() throws Exception { + // Send two requests + for (int i = 0; i < 2; i++) { + var request = plainTextRequest(HttpVersion.HTTP_1_1, ""); + var response = client.send(request); + assertEquals(RESPONSE_CONTENTS, readBody(response)); + } + + // Each request should use a new connection since server sends Connection: close + assertEquals(2, trackingHandler.requestCount()); + assertEquals(2, trackingHandler.connectionCount(), "Should create new connection for each request"); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionPoolExhaustionHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionPoolExhaustionHttp11Test.java new file mode 100644 index 000000000..d2a84ea7e --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionPoolExhaustionHttp11Test.java @@ -0,0 +1,73 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.DelayedResponseHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; + +/** + * Tests that requests block when connection pool is exhausted, then succeed. + */ +public class ConnectionPoolExhaustionHttp11Test extends BaseHttpClientIntegTest { + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new DelayedResponseHttp11ClientHandler( + new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS), + 200)); // 200ms delay + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .maxConnectionsPerRoute(1); // Only 1 connection allowed per route + } + + @Test + void blocksWhenPoolExhaustedThenSucceeds() throws Exception { + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + // First request holds the only connection for 200ms + var future1 = CompletableFuture.supplyAsync(() -> { + try { + var request = plainTextRequest(HttpVersion.HTTP_1_1, ""); + var response = client.send(request); + return readBody(response); + } catch (Exception e) { + throw new RuntimeException(e); + } + }, executor); + + // Small delay to ensure first request acquires connection + Thread.sleep(50); + + // Second request must wait for first to complete + var future2 = CompletableFuture.supplyAsync(() -> { + try { + var request = plainTextRequest(HttpVersion.HTTP_1_1, ""); + var response = client.send(request); + return readBody(response); + } catch (Exception e) { + throw new RuntimeException(e); + } + }, executor); + + assertEquals(RESPONSE_CONTENTS, future1.join()); + assertEquals(RESPONSE_CONTENTS, future2.join()); + } + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionPoolHighConcurrencyReuseTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionPoolHighConcurrencyReuseTest.java new file mode 100644 index 000000000..b16882101 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionPoolHighConcurrencyReuseTest.java @@ -0,0 +1,90 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.ConnectionTrackingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; + +/** + * Tests that HTTP/1.1 connections are properly reused under high concurrency + * when concurrency exceeds maxConnections. + * + *

      This tests the fix for a race condition where threads waiting on acquirePermit() + * would create new connections instead of reusing ones released while waiting. + */ +public class ConnectionPoolHighConcurrencyReuseTest extends BaseHttpClientIntegTest { + + private static final int MAX_CONNECTIONS = 5; + private static final int CONCURRENCY = 50; + private static final int REQUESTS_PER_THREAD = 10; + + private ConnectionTrackingHttp11ClientHandler trackingHandler; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + trackingHandler = new ConnectionTrackingHttp11ClientHandler( + new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS)); + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> trackingHandler); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .maxConnectionsPerRoute(MAX_CONNECTIONS) + .maxTotalConnections(MAX_CONNECTIONS); + } + + @Test + void connectionsAreReusedUnderHighConcurrency() throws Exception { + int totalRequests = CONCURRENCY * REQUESTS_PER_THREAD; + var successCount = new AtomicInteger(0); + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + var futures = new CompletableFuture[CONCURRENCY]; + + for (int i = 0; i < CONCURRENCY; i++) { + futures[i] = CompletableFuture.runAsync(() -> { + for (int j = 0; j < REQUESTS_PER_THREAD; j++) { + try { + var request = plainTextRequest(HttpVersion.HTTP_1_1, ""); + var response = client.send(request); + if (RESPONSE_CONTENTS.equals(readBody(response))) { + successCount.incrementAndGet(); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }, executor); + } + + CompletableFuture.allOf(futures).join(); + } + + assertEquals(totalRequests, successCount.get(), "All requests should succeed"); + assertEquals(totalRequests, trackingHandler.requestCount(), "Server should receive all requests"); + + // Connections should be reused, not created for every request. + // We should have at most maxConnections connections. + int connectionCount = trackingHandler.connectionCount(); + assertTrue(connectionCount <= MAX_CONNECTIONS, + "Should create at most " + MAX_CONNECTIONS + " connections, but created " + connectionCount); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionPoolReuseHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionPoolReuseHttp11Test.java new file mode 100644 index 000000000..388c4a0c3 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ConnectionPoolReuseHttp11Test.java @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.ConnectionTrackingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; + +/** + * Tests that HTTP/1.1 connections are reused across multiple requests. + */ +public class ConnectionPoolReuseHttp11Test extends BaseHttpClientIntegTest { + + private static final int NUM_REQUESTS = 5; + + private ConnectionTrackingHttp11ClientHandler trackingHandler; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + trackingHandler = new ConnectionTrackingHttp11ClientHandler( + new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS)); + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> trackingHandler); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .maxConnectionsPerRoute(1); + } + + @Test + void connectionIsReusedForMultipleRequests() throws Exception { + for (int i = 0; i < NUM_REQUESTS; i++) { + var request = plainTextRequest(HttpVersion.HTTP_1_1, REQUEST_CONTENTS); + var response = client.send(request); + assertEquals(RESPONSE_CONTENTS, readBody(response)); + } + + assertEquals(NUM_REQUESTS, trackingHandler.requestCount(), "Should have received all requests"); + assertEquals(1, trackingHandler.connectionCount(), "Should reuse single connection"); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ContinueHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ContinueHttp11Test.java new file mode 100644 index 000000000..53c150db8 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ContinueHttp11Test.java @@ -0,0 +1,62 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.ContinueHttp11ClientHandler; +import software.amazon.smithy.java.io.datastream.DataStream; + +/** + * Tests HTTP/1.1 100-continue handling. + */ +public class ContinueHttp11Test extends BaseHttpClientIntegTest { + + private ContinueHttp11ClientHandler handler; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + handler = new ContinueHttp11ClientHandler(RESPONSE_CONTENTS); + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> handler); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1); + } + + @Test + void handles100ContinueCorrectly() throws Exception { + var headers = HttpHeaders.ofModifiable(); + headers.addHeader("content-type", "text/plain"); + headers.addHeader("expect", "100-continue"); + headers.addHeader("content-length", String.valueOf(REQUEST_CONTENTS.length())); + + var request = HttpRequest.builder() + .httpVersion(HttpVersion.HTTP_1_1) + .uri(new URI(uri())) + .method("POST") + .headers(headers) + .body(DataStream.ofString(REQUEST_CONTENTS)) + .build(); + + var response = client.send(request); + + assertEquals(RESPONSE_CONTENTS, readBody(response)); + assertEquals(REQUEST_CONTENTS, handler.capturedBody().toString(StandardCharsets.UTF_8)); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/EmptyBodyHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/EmptyBodyHttp11Test.java new file mode 100644 index 000000000..187c8b987 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/EmptyBodyHttp11Test.java @@ -0,0 +1,73 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.URI; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.EmptyResponseHttp11ClientHandler; +import software.amazon.smithy.java.io.datastream.DataStream; + +/** + * Tests empty request and response bodies. + */ +public class EmptyBodyHttp11Test extends BaseHttpClientIntegTest { + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new EmptyResponseHttp11ClientHandler()); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1); + } + + @Test + void handlesEmptyRequestAndResponseBody() throws Exception { + // Request with no body + var request = HttpRequest.builder() + .httpVersion(HttpVersion.HTTP_1_1) + .uri(new URI(uri())) + .method("GET") + .headers(HttpHeaders.ofModifiable()) + .body(DataStream.ofEmpty()) + .build(); + + var response = client.send(request); + + assertEquals(204, response.statusCode()); + assertEquals("", readBody(response)); + } + + @Test + void handlesPostWithEmptyBody() throws Exception { + var headers = HttpHeaders.ofModifiable(); + headers.addHeader("content-length", "0"); + + var request = HttpRequest.builder() + .httpVersion(HttpVersion.HTTP_1_1) + .uri(new URI(uri())) + .method("POST") + .headers(headers) + .body(DataStream.ofEmpty()) + .build(); + + var response = client.send(request); + + assertEquals(204, response.statusCode()); + assertEquals("", readBody(response)); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/HighConcurrencyHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/HighConcurrencyHttp11Test.java new file mode 100644 index 000000000..245648b45 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/HighConcurrencyHttp11Test.java @@ -0,0 +1,94 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; + +/** + * Stress test with many concurrent HTTP/1.1 requests. + * Tests various pool sizes to verify connection reuse and permit release. + */ +public class HighConcurrencyHttp11Test extends BaseHttpClientIntegTest { + + // Set by parameterized test + private int poolSize = 20; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS)); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .maxConnectionsPerRoute(poolSize) + .maxTotalConnections(poolSize); + } + + static Stream poolConfigurations() { + return Stream.of( + // poolSize, numRequests - tests connection reuse when requests > pool + Arguments.of(5, 50), // 10x reuse required + Arguments.of(10, 100), // 10x reuse required + Arguments.of(20, 100), // 5x reuse required + Arguments.of(50, 100) // 2x reuse required + ); + } + + @ParameterizedTest(name = "pool={0}, requests={1}") + @MethodSource("poolConfigurations") + void handlesMoreRequestsThanPoolSize(int poolSize, int numRequests) throws Exception { + this.poolSize = poolSize; + + // Recreate client with new pool size + if (client != null) { + client.close(); + } + setUp(); + + var futures = new ArrayList>(); + var successCount = new AtomicInteger(); + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < numRequests; i++) { + futures.add(CompletableFuture.supplyAsync(() -> { + try { + var request = plainTextRequest(HttpVersion.HTTP_1_1, ""); + var response = client.send(request); + var body = readBody(response); + successCount.incrementAndGet(); + return body; + } catch (Exception e) { + throw new RuntimeException(e); + } + }, executor)); + } + + for (var future : futures) { + assertEquals(RESPONSE_CONTENTS, future.join()); + } + } + + assertEquals(numRequests, successCount.get()); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/IdleConnectionCleanupHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/IdleConnectionCleanupHttp11Test.java new file mode 100644 index 000000000..f21c579df --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/IdleConnectionCleanupHttp11Test.java @@ -0,0 +1,60 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.Duration; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.ConnectionTrackingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; + +/** + * Tests that idle connections are cleaned up after timeout. + */ +public class IdleConnectionCleanupHttp11Test extends BaseHttpClientIntegTest { + + private ConnectionTrackingHttp11ClientHandler trackingHandler; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + trackingHandler = new ConnectionTrackingHttp11ClientHandler( + new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS)); + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> trackingHandler); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .maxIdleTime(Duration.ofMillis(100)); // Very short idle timeout + } + + @Test + void idleConnectionsAreCleanedUp() throws Exception { + // First request creates connection + var request1 = plainTextRequest(HttpVersion.HTTP_1_1, ""); + var response1 = client.send(request1); + assertEquals(RESPONSE_CONTENTS, readBody(response1)); + assertEquals(1, trackingHandler.connectionCount()); + + // Wait for idle timeout + cleanup interval + Thread.sleep(300); + + // Second request should create new connection (old one was cleaned up) + var request2 = plainTextRequest(HttpVersion.HTTP_1_1, ""); + var response2 = client.send(request2); + assertEquals(RESPONSE_CONTENTS, readBody(response2)); + + assertEquals(2, trackingHandler.connectionCount(), "Should create new connection after idle cleanup"); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/LargeHeadersHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/LargeHeadersHttp11Test.java new file mode 100644 index 000000000..23f61ce78 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/LargeHeadersHttp11Test.java @@ -0,0 +1,51 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.LargeHeadersHttp11ClientHandler; + +/** + * Tests handling of responses with many large headers. + */ +public class LargeHeadersHttp11Test extends BaseHttpClientIntegTest { + + private static final int HEADER_COUNT = 50; + private static final int HEADER_VALUE_SIZE = 1000; // 1KB per header value + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new LargeHeadersHttp11ClientHandler( + RESPONSE_CONTENTS, + HEADER_COUNT, + HEADER_VALUE_SIZE)); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1); + } + + @Test + void handlesLargeResponseHeaders() throws Exception { + var request = plainTextRequest(HttpVersion.HTTP_1_1, ""); + var response = client.send(request); + + assertEquals(RESPONSE_CONTENTS, readBody(response)); + + // Verify at least one large header was received + String value = response.headers().firstValue("x-large-header-0"); + assertEquals(HEADER_VALUE_SIZE, value.length()); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/PerRouteLimitsHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/PerRouteLimitsHttp11Test.java new file mode 100644 index 000000000..1f3bc885c --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/PerRouteLimitsHttp11Test.java @@ -0,0 +1,111 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.InetAddress; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.TestUtils; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.ConnectionTrackingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; + +/** + * Tests that per-route connection limits are independent. + */ +public class PerRouteLimitsHttp11Test { + + private NettyTestServer server1; + private NettyTestServer server2; + private HttpClient client; + private ConnectionTrackingHttp11ClientHandler handler1; + private ConnectionTrackingHttp11ClientHandler handler2; + + @BeforeEach + void setUp() throws Exception { + handler1 = new ConnectionTrackingHttp11ClientHandler( + new TextResponseHttp11ClientHandler("response1")); + handler2 = new ConnectionTrackingHttp11ClientHandler( + new TextResponseHttp11ClientHandler("response2")); + + server1 = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> handler1) + .build(); + server1.start(); + + server2 = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> handler2) + .build(); + server2.start(); + + // Map different hostnames to loopback + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "host1.test", + List.of(InetAddress.getLoopbackAddress()), + "host2.test", + List.of(InetAddress.getLoopbackAddress()))); + + client = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .maxConnectionsPerRoute(1) // Only 1 connection per route + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .dnsResolver(staticDns) + .build()) + .build(); + } + + @AfterEach + void tearDown() throws Exception { + if (client != null) { + client.close(); + } + if (server1 != null) { + server1.stop(); + } + if (server2 != null) { + server2.stop(); + } + } + + @Test + void differentRoutesHaveIndependentLimits() throws Exception { + // Send request to host1 + var request1 = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, "http://host1.test:" + server1.getPort(), ""); + var response1 = client.send(request1); + assertEquals("response1", readBody(response1)); + + // Send request to host2 - should work even though host1 has a connection + var request2 = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, "http://host2.test:" + server2.getPort(), ""); + var response2 = client.send(request2); + assertEquals("response2", readBody(response2)); + + // Both routes should have 1 connection each + assertEquals(1, handler1.connectionCount()); + assertEquals(1, handler2.connectionCount()); + } + + private String readBody(software.amazon.smithy.java.http.api.HttpResponse response) { + var buf = response.body().asByteBuffer(); + var bytes = new byte[buf.remaining()]; + buf.get(bytes); + return new String(bytes, java.nio.charset.StandardCharsets.UTF_8); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ReadTimeoutHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ReadTimeoutHttp11Test.java new file mode 100644 index 000000000..f12897469 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ReadTimeoutHttp11Test.java @@ -0,0 +1,47 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.net.SocketTimeoutException; +import java.time.Duration; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.DelayedResponseHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; + +/** + * Tests that read timeout throws SocketTimeoutException. + */ +public class ReadTimeoutHttp11Test extends BaseHttpClientIntegTest { + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new DelayedResponseHttp11ClientHandler( + new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS), + 2000)); // 2s delay + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .readTimeout(Duration.ofMillis(100)); // 100ms timeout, server delays 2s + } + + @Test + void readTimeoutThrowsException() { + var request = plainTextRequest(HttpVersion.HTTP_1_1, ""); + + assertThrows(SocketTimeoutException.class, () -> client.send(request)); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11ClearTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11ClearTest.java new file mode 100644 index 000000000..deeb0f721 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11ClearTest.java @@ -0,0 +1,51 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.MultiplexingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.RequestCapturingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; + +/** + * Tests basic HTTP/1.1 request/response over cleartext (no TLS). + */ +public class RequestResponseHttp11ClearTest extends BaseHttpClientIntegTest { + + private RequestCapturingHttp11ClientHandler requestCapturingHandler; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + requestCapturingHandler = new RequestCapturingHttp11ClientHandler(); + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new MultiplexingHttp11ClientHandler( + requestCapturingHandler, + new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS))); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1); + } + + @Test + void canSendRequestAndReadResponse() throws Exception { + var request = plainTextRequest(HttpVersion.HTTP_1_1, REQUEST_CONTENTS); + + var response = client.send(request); + + assertEquals(REQUEST_CONTENTS, requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); + assertEquals(RESPONSE_CONTENTS, readBody(response)); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11WithTlsTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11WithTlsTest.java new file mode 100644 index 000000000..4eba073c6 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11WithTlsTest.java @@ -0,0 +1,76 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.TestUtils; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; +import software.amazon.smithy.java.http.client.it.server.h1.MultiplexingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.RequestCapturingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; + +/** + * Tests basic HTTP/1.1 request/response over TLS. + */ +public class RequestResponseHttp11WithTlsTest extends BaseHttpClientIntegTest { + + private static TestCertificateGenerator.CertificateBundle bundle; + private RequestCapturingHttp11ClientHandler requestCapturingHandler; + + @BeforeAll + static void beforeAll() throws Exception { + bundle = TestCertificateGenerator.generateCertificates(); + } + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + requestCapturingHandler = new RequestCapturingHttp11ClientHandler(); + try { + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new MultiplexingHttp11ClientHandler( + requestCapturingHandler, + new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS))) + .sslContextBuilder(TestUtils.createServerSslContextBuilder(bundle)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + try { + return builder + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .sslContext(TestUtils.createClientSslContext(bundle)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + protected String uri() { + return "https://localhost:" + server.getPort(); + } + + @Test + void canSendRequestAndReadResponse() throws Exception { + var request = plainTextRequest(HttpVersion.HTTP_1_1, REQUEST_CONTENTS); + + var response = client.send(request); + + assertEquals(REQUEST_CONTENTS, requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); + assertEquals(RESPONSE_CONTENTS, readBody(response)); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/StatusCodesHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/StatusCodesHttp11Test.java new file mode 100644 index 000000000..4b1e10e55 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/StatusCodesHttp11Test.java @@ -0,0 +1,124 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.TestUtils; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.StatusCodeHttp11ClientHandler; + +/** + * Tests various HTTP status codes with empty bodies. + */ +public class StatusCodesHttp11Test { + + private HttpClient client; + private NettyTestServer server; + + @BeforeEach + void setUp() { + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + + client = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .dnsResolver(staticDns) + .build()) + .build(); + } + + @AfterEach + void tearDown() throws Exception { + if (client != null) { + client.close(); + } + if (server != null) { + server.stop(); + } + } + + @Test + void handles200WithEmptyBody() throws Exception { + startServer(200); + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, "http://localhost:" + server.getPort(), ""); + var response = client.send(request); + + assertEquals(200, response.statusCode()); + assertEquals("", readBody(response)); + } + + @Test + void handles204NoContent() throws Exception { + startServer(204); + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, "http://localhost:" + server.getPort(), ""); + var response = client.send(request); + + assertEquals(204, response.statusCode()); + assertEquals("", readBody(response)); + } + + @Test + void handles304NotModified() throws Exception { + startServer(304); + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, "http://localhost:" + server.getPort(), ""); + var response = client.send(request); + + assertEquals(304, response.statusCode()); + assertEquals("", readBody(response)); + } + + @Test + void handles404NotFound() throws Exception { + startServer(404); + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, "http://localhost:" + server.getPort(), ""); + var response = client.send(request); + + assertEquals(404, response.statusCode()); + } + + @Test + void handles500InternalServerError() throws Exception { + startServer(500); + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, "http://localhost:" + server.getPort(), ""); + var response = client.send(request); + + assertEquals(500, response.statusCode()); + } + + private void startServer(int statusCode) throws Exception { + server = NettyTestServer.builder() + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new StatusCodeHttp11ClientHandler(statusCode)) + .build(); + server.start(); + } + + private String readBody(software.amazon.smithy.java.http.api.HttpResponse response) { + var buf = response.body().asByteBuffer(); + var bytes = new byte[buf.remaining()]; + buf.get(bytes); + return new String(bytes, StandardCharsets.UTF_8); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/TrailerHeadersHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/TrailerHeadersHttp11Test.java new file mode 100644 index 000000000..d3b0dafd2 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/TrailerHeadersHttp11Test.java @@ -0,0 +1,58 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Map; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.TrailerResponseHttp11ClientHandler; + +/** + * Tests HTTP/1.1 chunked response with trailer headers. + */ +public class TrailerHeadersHttp11Test extends BaseHttpClientIntegTest { + + private static final Map TRAILERS = Map.of( + "x-checksum", + "abc123", + "x-request-id", + "req-456"); + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new TrailerResponseHttp11ClientHandler(RESPONSE_CONTENTS, TRAILERS)); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1); + } + + @Test + void readsChunkedResponseWithTrailers() throws Exception { + var request = plainTextRequest(HttpVersion.HTTP_1_1, ""); + + try (var exchange = client.newExchange(request)) { + exchange.requestBody().close(); + + var body = new String(exchange.responseBody().readAllBytes()); + assertEquals(RESPONSE_CONTENTS, body); + + var trailers = exchange.responseTrailerHeaders(); + assertNotNull(trailers, "Should have trailer headers"); + assertEquals("abc123", trailers.firstValue("x-checksum")); + assertEquals("req-456", trailers.firstValue("x-request-id")); + } + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/BaseHttpClientIntegTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/BaseHttpClientIntegTest.java new file mode 100644 index 000000000..25508b0ec --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/BaseHttpClientIntegTest.java @@ -0,0 +1,97 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import java.io.IOException; +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.TestUtils; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; + +/** + * Base class for HTTP client integration tests. + * Provides common setup/teardown and utility methods. + */ +public abstract class BaseHttpClientIntegTest { + + protected static final String RESPONSE_CONTENTS = "Test response body"; + protected static final String REQUEST_CONTENTS = "Test request body"; + + protected NettyTestServer server; + protected HttpClient client; + + /** + * Configure the test server. + */ + protected abstract NettyTestServer.Builder configureServer(NettyTestServer.Builder builder); + + /** + * Configure the connection pool. + */ + protected abstract HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder); + + @BeforeEach + void setUp() throws Exception { + server = configureServer(NettyTestServer.builder()).build(); + server.start(); + + DnsResolver staticDns = DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress()))); + + var poolBuilder = HttpConnectionPool.builder() + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .dnsResolver(staticDns); + + poolBuilder = configurePool(poolBuilder); + + client = HttpClient.builder() + .connectionPool(poolBuilder.build()) + .build(); + } + + @AfterEach + void tearDown() throws Exception { + if (client != null) { + client.close(); + } + if (server != null) { + server.stop(); + } + } + + protected String uri(String path) { + return "http://localhost:" + server.getPort() + path; + } + + protected String uri() { + return uri(""); + } + + protected HttpRequest plainTextRequest(HttpVersion version, String body) { + return TestUtils.plainTextRequest(version, uri(), body); + } + + protected String readBody(HttpResponse response) throws IOException { + try (var body = response.body().asInputStream()) { + return new String(body.readAllBytes(), StandardCharsets.UTF_8); + } + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/ConcurrentStreamsHttp2Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/ConcurrentStreamsHttp2Test.java new file mode 100644 index 000000000..5a70d40f3 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/ConcurrentStreamsHttp2Test.java @@ -0,0 +1,71 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h2.ConnectionTrackingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; + +/** + * Tests that multiple HTTP/2 streams are multiplexed on a single connection. + */ +public class ConcurrentStreamsHttp2Test extends BaseHttpClientIntegTest { + + private ConnectionTrackingHttp2ClientHandler trackingHandler; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + trackingHandler = new ConnectionTrackingHttp2ClientHandler( + new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS)); + return builder + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory(ctx -> trackingHandler); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder + .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) + .maxConnectionsPerRoute(1); // Force all streams onto single connection + } + + @Test + void multipleConcurrentStreamsOnSameConnection() throws Exception { + int numRequests = 10; + var futures = new ArrayList>(); + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < numRequests; i++) { + futures.add(CompletableFuture.supplyAsync(() -> { + try { + var request = plainTextRequest(HttpVersion.HTTP_2, REQUEST_CONTENTS); + var response = client.send(request); + return readBody(response); + } catch (Exception e) { + throw new RuntimeException(e); + } + }, executor)); + } + + for (var future : futures) { + assertEquals(RESPONSE_CONTENTS, future.join()); + } + } + + assertEquals(numRequests, trackingHandler.requestCount(), "Should have received all requests"); + assertEquals(1, trackingHandler.connectionCount(), "All streams should use single connection"); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/FlowControlHttp2Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/FlowControlHttp2Test.java new file mode 100644 index 000000000..daf3f4675 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/FlowControlHttp2Test.java @@ -0,0 +1,67 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.InputStream; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h2.LargeResponseHttp2ClientHandler; + +/** + * Tests HTTP/2 flow control with large response bodies and slow client reads. + */ +public class FlowControlHttp2Test extends BaseHttpClientIntegTest { + + private static final int RESPONSE_SIZE = 1024 * 1024; // 1MB + private static final int CHUNK_SIZE = 16384; // 16KB chunks + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + return builder + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory(ctx -> new LargeResponseHttp2ClientHandler(RESPONSE_SIZE, CHUNK_SIZE)); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE); + } + + @Test + void largeResponseBodyWithFlowControl() throws Exception { + var request = plainTextRequest(HttpVersion.HTTP_2, ""); + var response = client.send(request); + + // Read slowly with small buffer to exercise flow control + byte[] buffer = new byte[1024]; + int totalRead = 0; + int bytesRead; + + try (InputStream is = response.body().asInputStream()) { + while ((bytesRead = is.read(buffer)) != -1) { + // Verify data pattern + for (int i = 0; i < bytesRead; i++) { + byte expected = (byte) ((totalRead + i) & 0xFF); + assertEquals(expected, buffer[i], "Data mismatch at position " + (totalRead + i)); + } + totalRead += bytesRead; + + // Simulate slow client - delay every 64KB + if (totalRead % (64 * 1024) == 0) { + Thread.sleep(10); + } + } + } + + assertEquals(RESPONSE_SIZE, totalRead, "Should receive complete response body"); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/GoawayHttp2Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/GoawayHttp2Test.java new file mode 100644 index 000000000..0da419c63 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/GoawayHttp2Test.java @@ -0,0 +1,55 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h2.GoawayAfterFirstRequestHandler; + +/** + * Tests that GOAWAY is handled gracefully - subsequent requests use new connection. + */ +public class GoawayHttp2Test extends BaseHttpClientIntegTest { + + private GoawayAfterFirstRequestHandler handler; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + handler = new GoawayAfterFirstRequestHandler(RESPONSE_CONTENTS); + return builder + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory(ctx -> handler); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE); + } + + @Test + void handlesGoawayGracefully() throws Exception { + // First request - server will send GOAWAY after response + var request1 = plainTextRequest(HttpVersion.HTTP_2, ""); + var response1 = client.send(request1); + assertEquals(RESPONSE_CONTENTS, readBody(response1)); + + // Wait for GOAWAY to be processed + Thread.sleep(200); + + // Second request - should use new connection due to GOAWAY + var request2 = plainTextRequest(HttpVersion.HTTP_2, ""); + var response2 = client.send(request2); + assertEquals(RESPONSE_CONTENTS, readBody(response2)); + + assertEquals(2, handler.connectionCount(), "Should use new connection after GOAWAY"); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/HighConcurrencyHttp2Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/HighConcurrencyHttp2Test.java new file mode 100644 index 000000000..dcb7308e6 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/HighConcurrencyHttp2Test.java @@ -0,0 +1,91 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; + +/** + * Stress test with many concurrent HTTP/2 requests. + * Tests various pool sizes to verify stream multiplexing and connection reuse. + */ +public class HighConcurrencyHttp2Test extends BaseHttpClientIntegTest { + + private int poolSize = 5; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + return builder + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory(ctx -> new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS)); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder + .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) + .maxConnectionsPerRoute(poolSize) + .maxTotalConnections(poolSize); + } + + static Stream poolConfigurations() { + return Stream.of( + // poolSize, numRequests - H2 multiplexes so fewer connections needed + Arguments.of(1, 50), // All on single connection + Arguments.of(2, 100), // 50 streams per connection + Arguments.of(5, 100), // 20 streams per connection + Arguments.of(10, 200) // 20 streams per connection + ); + } + + @ParameterizedTest(name = "pool={0}, requests={1}") + @MethodSource("poolConfigurations") + void handlesHighConcurrency(int poolSize, int numRequests) throws Exception { + this.poolSize = poolSize; + if (client != null) + client.close(); + setUp(); + + var futures = new ArrayList>(); + var successCount = new AtomicInteger(); + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < numRequests; i++) { + futures.add(CompletableFuture.supplyAsync(() -> { + try { + var request = plainTextRequest(HttpVersion.HTTP_2, ""); + var response = client.send(request); + var body = readBody(response); + successCount.incrementAndGet(); + return body; + } catch (Exception e) { + throw new RuntimeException(e); + } + }, executor)); + } + + for (var future : futures) { + assertEquals(RESPONSE_CONTENTS, future.join()); + } + } + + assertEquals(numRequests, successCount.get()); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/MaxConcurrentStreamsHttp2Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/MaxConcurrentStreamsHttp2Test.java new file mode 100644 index 000000000..9c65c5352 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/MaxConcurrentStreamsHttp2Test.java @@ -0,0 +1,71 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h2.DelayedResponseHttp2ClientHandler; + +/** + * Tests HTTP/2 concurrent streams behavior. + */ +public class MaxConcurrentStreamsHttp2Test extends BaseHttpClientIntegTest { + + private DelayedResponseHttp2ClientHandler handler; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + handler = new DelayedResponseHttp2ClientHandler(RESPONSE_CONTENTS, 100); + return builder + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory(ctx -> handler); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder + .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) + .maxConnectionsPerRoute(1); // Force single connection + } + + @Test + void manyConcurrentStreamsOnSingleConnection() throws Exception { + int numRequests = 50; + var futures = new ArrayList>(); + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < numRequests; i++) { + futures.add(CompletableFuture.supplyAsync(() -> { + try { + var request = plainTextRequest(HttpVersion.HTTP_2, ""); + var response = client.send(request); + return readBody(response); + } catch (Exception e) { + throw new RuntimeException(e); + } + }, executor)); + } + + for (var future : futures) { + assertEquals(RESPONSE_CONTENTS, future.join()); + } + } + + // Verify we actually had concurrent streams (not serialized) + assertTrue(handler.maxObservedConcurrent() > 1, + "Should have multiple concurrent streams, observed: " + handler.maxObservedConcurrent()); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2ClearTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2ClearTest.java new file mode 100644 index 000000000..2b01b4396 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2ClearTest.java @@ -0,0 +1,56 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h2.MultiplexingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.RequestCapturingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; + +/** + * Tests basic HTTP/2 request/response over cleartext (h2c with prior knowledge). + */ +public class RequestResponseHttp2ClearTest extends BaseHttpClientIntegTest { + + private RequestCapturingHttp2ClientHandler requestCapturingHandler; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); + return builder + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory(ctx -> new MultiplexingHttp2ClientHandler( + requestCapturingHandler, + new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS))); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE); + } + + @Test + void canSendRequestAndReadResponse() throws Exception { + var request = plainTextRequest(HttpVersion.HTTP_2, REQUEST_CONTENTS); + + var response = client.send(request); + var responseBody = readBody(response); + + // Wait for server to receive all request data + requestCapturingHandler.streamCompleted().join(); + + assertEquals(REQUEST_CONTENTS, requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); + assertEquals(RESPONSE_CONTENTS, responseBody); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithAlpnTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithAlpnTest.java new file mode 100644 index 000000000..9110295e7 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithAlpnTest.java @@ -0,0 +1,81 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.TestUtils; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; +import software.amazon.smithy.java.http.client.it.server.h2.MultiplexingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.RequestCapturingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; + +/** + * Tests basic HTTP/2 request/response over TLS with ALPN negotiation. + */ +public class RequestResponseHttp2WithAlpnTest extends BaseHttpClientIntegTest { + + private static TestCertificateGenerator.CertificateBundle bundle; + private RequestCapturingHttp2ClientHandler requestCapturingHandler; + + @BeforeAll + static void beforeAll() throws Exception { + bundle = TestCertificateGenerator.generateCertificates(); + } + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); + try { + return builder + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.ALPN) + .http2HandlerFactory(ctx -> new MultiplexingHttp2ClientHandler( + requestCapturingHandler, + new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS))) + .sslContextBuilder(TestUtils.createServerSslContextBuilder(bundle)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + try { + return builder + .httpVersionPolicy(HttpVersionPolicy.AUTOMATIC) + .sslContext(TestUtils.createClientSslContext(bundle)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + protected String uri() { + return "https://localhost:" + server.getPort(); + } + + @Test + void canSendRequestAndReadResponse() throws Exception { + var request = plainTextRequest(HttpVersion.HTTP_2, REQUEST_CONTENTS); + + var response = client.send(request); + var responseBody = readBody(response); + + // Wait for server to receive all request data + requestCapturingHandler.streamCompleted().join(); + + assertEquals(REQUEST_CONTENTS, requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); + assertEquals(RESPONSE_CONTENTS, responseBody); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithTlsTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithTlsTest.java new file mode 100644 index 000000000..f37778525 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithTlsTest.java @@ -0,0 +1,81 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.TestUtils; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; +import software.amazon.smithy.java.http.client.it.server.h2.MultiplexingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.RequestCapturingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; + +/** + * Tests basic HTTP/2 request/response over TLS with prior knowledge (no ALPN). + */ +public class RequestResponseHttp2WithTlsTest extends BaseHttpClientIntegTest { + + private static TestCertificateGenerator.CertificateBundle bundle; + private RequestCapturingHttp2ClientHandler requestCapturingHandler; + + @BeforeAll + static void beforeAll() throws Exception { + bundle = TestCertificateGenerator.generateCertificates(); + } + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); + try { + return builder + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory(ctx -> new MultiplexingHttp2ClientHandler( + requestCapturingHandler, + new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS))) + .sslContextBuilder(TestUtils.createServerSslContextBuilder(bundle)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + try { + return builder + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_2) + .sslContext(TestUtils.createClientSslContext(bundle)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + protected String uri() { + return "https://localhost:" + server.getPort(); + } + + @Test + void canSendRequestAndReadResponse() throws Exception { + var request = plainTextRequest(HttpVersion.HTTP_2, REQUEST_CONTENTS); + + var response = client.send(request); + var responseBody = readBody(response); + + // Wait for server to receive all request data + requestCapturingHandler.streamCompleted().join(); + + assertEquals(REQUEST_CONTENTS, requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); + assertEquals(RESPONSE_CONTENTS, responseBody); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2ClearTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2ClearTest.java new file mode 100644 index 000000000..a3670afbb --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2ClearTest.java @@ -0,0 +1,57 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.smithy.java.http.client.it.TestUtils.IPSUM_LOREM; +import static software.amazon.smithy.java.http.client.it.TestUtils.streamingBody; + +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.TestUtils; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h2.MultiplexingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.RequestCapturingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; + +/** + * Tests HTTP/2 streaming request body over cleartext (h2c with prior knowledge). + */ +public class RequestStreamingHttp2ClearTest extends BaseHttpClientIntegTest { + + private RequestCapturingHttp2ClientHandler requestCapturingHandler; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); + return builder + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory(ctx -> new MultiplexingHttp2ClientHandler( + requestCapturingHandler, + new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS))); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE); + } + + @Test + void canSendStreamingRequestAndReadResponse() throws Exception { + var request = TestUtils.request(HttpVersion.HTTP_2, uri(), streamingBody(IPSUM_LOREM)); + + var response = client.send(request); + requestCapturingHandler.streamCompleted().join(); + + assertEquals(String.join("", IPSUM_LOREM), + requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); + assertEquals(RESPONSE_CONTENTS, readBody(response)); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithAlpnTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithAlpnTest.java new file mode 100644 index 000000000..12118c9fe --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithAlpnTest.java @@ -0,0 +1,81 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.smithy.java.http.client.it.TestUtils.IPSUM_LOREM; +import static software.amazon.smithy.java.http.client.it.TestUtils.streamingBody; + +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.TestUtils; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; +import software.amazon.smithy.java.http.client.it.server.h2.MultiplexingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.RequestCapturingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; + +/** + * Tests HTTP/2 streaming request body over TLS with ALPN negotiation. + */ +public class RequestStreamingHttp2WithAlpnTest extends BaseHttpClientIntegTest { + + private static TestCertificateGenerator.CertificateBundle bundle; + private RequestCapturingHttp2ClientHandler requestCapturingHandler; + + @BeforeAll + static void beforeAll() throws Exception { + bundle = TestCertificateGenerator.generateCertificates(); + } + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); + try { + return builder + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.ALPN) + .http2HandlerFactory(ctx -> new MultiplexingHttp2ClientHandler( + requestCapturingHandler, + new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS))) + .sslContextBuilder(TestUtils.createServerSslContextBuilder(bundle)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + try { + return builder + .httpVersionPolicy(HttpVersionPolicy.AUTOMATIC) + .sslContext(TestUtils.createClientSslContext(bundle)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + protected String uri() { + return "https://localhost:" + server.getPort(); + } + + @Test + void canSendStreamingRequestAndReadResponse() throws Exception { + var request = TestUtils.request(HttpVersion.HTTP_2, uri(), streamingBody(IPSUM_LOREM)); + + var response = client.send(request); + requestCapturingHandler.streamCompleted().join(); + + assertEquals(String.join("", IPSUM_LOREM), + requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); + assertEquals(RESPONSE_CONTENTS, readBody(response)); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithTlsTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithTlsTest.java new file mode 100644 index 000000000..4cc38df0c --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithTlsTest.java @@ -0,0 +1,81 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.smithy.java.http.client.it.TestUtils.IPSUM_LOREM; +import static software.amazon.smithy.java.http.client.it.TestUtils.streamingBody; + +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.TestUtils; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; +import software.amazon.smithy.java.http.client.it.server.h2.MultiplexingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.RequestCapturingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; + +/** + * Tests HTTP/2 streaming request body over TLS with prior knowledge (no ALPN). + */ +public class RequestStreamingHttp2WithTlsTest extends BaseHttpClientIntegTest { + + private static TestCertificateGenerator.CertificateBundle bundle; + private RequestCapturingHttp2ClientHandler requestCapturingHandler; + + @BeforeAll + static void beforeAll() throws Exception { + bundle = TestCertificateGenerator.generateCertificates(); + } + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); + try { + return builder + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory(ctx -> new MultiplexingHttp2ClientHandler( + requestCapturingHandler, + new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS))) + .sslContextBuilder(TestUtils.createServerSslContextBuilder(bundle)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + try { + return builder + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_2) + .sslContext(TestUtils.createClientSslContext(bundle)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + protected String uri() { + return "https://localhost:" + server.getPort(); + } + + @Test + void canSendStreamingRequestAndReadResponse() throws Exception { + var request = TestUtils.request(HttpVersion.HTTP_2, uri(), streamingBody(IPSUM_LOREM)); + + var response = client.send(request); + requestCapturingHandler.streamCompleted().join(); + + assertEquals(String.join("", IPSUM_LOREM), + requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); + assertEquals(RESPONSE_CONTENTS, readBody(response)); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RstStreamHttp2Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RstStreamHttp2Test.java new file mode 100644 index 000000000..4b4e8b24a --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RstStreamHttp2Test.java @@ -0,0 +1,43 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import io.netty.handler.codec.http2.Http2Error; +import java.io.IOException; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h2.RstStreamHttp2ClientHandler; + +/** + * Tests that RST_STREAM is handled correctly. + */ +public class RstStreamHttp2Test extends BaseHttpClientIntegTest { + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + return builder + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory(ctx -> new RstStreamHttp2ClientHandler(Http2Error.CANCEL)); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE); + } + + @Test + void handlesRstStreamError() { + var request = plainTextRequest(HttpVersion.HTTP_2, ""); + + assertThrows(IOException.class, () -> client.send(request)); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/ServerCloseMidStreamHttp2Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/ServerCloseMidStreamHttp2Test.java new file mode 100644 index 000000000..557f68ca9 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/ServerCloseMidStreamHttp2Test.java @@ -0,0 +1,46 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h2.PartialResponseHttp2ClientHandler; + +/** + * Tests that server closing connection mid-response throws IOException. + */ +public class ServerCloseMidStreamHttp2Test extends BaseHttpClientIntegTest { + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + return builder + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory(ctx -> new PartialResponseHttp2ClientHandler()); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE); + } + + @Test + void handlesServerClosingConnectionMidResponse() { + var request = plainTextRequest(HttpVersion.HTTP_2, ""); + + assertThrows(IOException.class, () -> { + var response = client.send(request); + // Try to read full body - should fail since server closed mid-stream + response.body().asInputStream().readAllBytes(); + }); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/StreamingResponseHttp2Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/StreamingResponseHttp2Test.java new file mode 100644 index 000000000..719f34ef9 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/StreamingResponseHttp2Test.java @@ -0,0 +1,67 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h2.StreamingResponseHttp2ClientHandler; + +/** + * Tests incremental reading of streaming response. + */ +public class StreamingResponseHttp2Test extends BaseHttpClientIntegTest { + + private static final List CHUNKS = List.of("chunk1-", "chunk2-", "chunk3-", "chunk4-", "chunk5"); + private static final long DELAY_MS = 50; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + return builder + .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) + .http2HandlerFactory(ctx -> new StreamingResponseHttp2ClientHandler(CHUNKS, DELAY_MS)); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE); + } + + @Test + void canReadStreamingResponseIncrementally() throws Exception { + var request = plainTextRequest(HttpVersion.HTTP_2, ""); + var response = client.send(request); + + var receivedChunks = new ArrayList(); + byte[] buffer = new byte[64]; + int bytesRead; + + try (InputStream is = response.body().asInputStream()) { + StringBuilder current = new StringBuilder(); + while ((bytesRead = is.read(buffer)) != -1) { + current.append(new String(buffer, 0, bytesRead)); + while (current.indexOf("-") >= 0) { + int idx = current.indexOf("-"); + receivedChunks.add(current.substring(0, idx + 1)); + current.delete(0, idx + 1); + } + } + if (!current.isEmpty()) { + receivedChunks.add(current.toString()); + } + } + + assertEquals(String.join("", CHUNKS), String.join("", receivedChunks)); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/TrailerHeadersHttp2Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/TrailerHeadersHttp2Test.java new file mode 100644 index 000000000..f2b1b8a73 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/TrailerHeadersHttp2Test.java @@ -0,0 +1,58 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Map; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h2.TrailerResponseHttp2ClientHandler; + +/** + * Tests HTTP/2 response with trailer headers. + */ +public class TrailerHeadersHttp2Test extends BaseHttpClientIntegTest { + + private static final Map TRAILERS = Map.of( + "x-checksum", + "abc123", + "x-request-id", + "req-456"); + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + return builder + .httpVersion(HttpVersion.HTTP_2) + .http2HandlerFactory(ctx -> new TrailerResponseHttp2ClientHandler(RESPONSE_CONTENTS, TRAILERS)); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE); + } + + @Test + void readsResponseWithTrailers() throws Exception { + var request = plainTextRequest(HttpVersion.HTTP_2, ""); + + try (var exchange = client.newExchange(request)) { + exchange.requestBody().close(); + + var body = new String(exchange.responseBody().readAllBytes()); + assertEquals(RESPONSE_CONTENTS, body); + + var trailers = exchange.responseTrailerHeaders(); + assertNotNull(trailers, "Should have trailer headers"); + assertEquals("abc123", trailers.firstValue("x-checksum")); + assertEquals("req-456", trailers.firstValue("x-request-id")); + } + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandler.java deleted file mode 100644 index b8547c045..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandler.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it.server; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.HttpContent; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.LastHttpContent; - -public interface Http11ClientHandler { - - void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request); - - void onRequest(ChannelHandlerContext ctx, HttpRequest request); - - void onContent(ChannelHandlerContext ctx, HttpContent content); - - void onLastContent(ChannelHandlerContext ctx, LastHttpContent content); - - void onException(ChannelHandlerContext ctx, Throwable cause); -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandler.java deleted file mode 100644 index 6cdade519..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandler.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it.server; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http2.Http2DataFrame; -import io.netty.handler.codec.http2.Http2HeadersFrame; - -public interface Http2ClientHandler { - - void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame); - - void onDataFrame(ChannelHandlerContext ctx, Http2DataFrame frame); - - void onException(ChannelHandlerContext ctx, Throwable cause); - -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/NettyTestServer.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/NettyTestServer.java index 514a8a0ba..98a5257a1 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/NettyTestServer.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/NettyTestServer.java @@ -15,7 +15,14 @@ import io.netty.util.concurrent.DefaultThreadFactory; import java.net.InetSocketAddress; import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.it.server.h1.Http11ClientHandlerFactory; +import software.amazon.smithy.java.http.client.it.server.h2.Http2ClientHandlerFactory; +/** + * Netty-based test server for HTTP client integration tests. + * + *

      Supports HTTP/1.1 and HTTP/2 with optional TLS and ALPN negotiation. + */ public class NettyTestServer { private static final NettyTestLogger LOGGER = NettyTestLogger.getLogger(NettyTestServer.class); private final EventLoopGroup bossGroup; diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/ServerInitializer.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/ServerInitializer.java index 33d0fe370..4002d503c 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/ServerInitializer.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/ServerInitializer.java @@ -21,7 +21,20 @@ import java.util.concurrent.ConcurrentHashMap; import javax.net.ssl.SSLException; import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.it.server.h1.Http11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.Http11ClientHandlerFactory; +import software.amazon.smithy.java.http.client.it.server.h1.Http11Handler; +import software.amazon.smithy.java.http.client.it.server.h2.Http2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.Http2ClientHandlerFactory; +import software.amazon.smithy.java.http.client.it.server.h2.Http2ConnectionFrameHandler; +import software.amazon.smithy.java.http.client.it.server.h2.Http2StreamFrameHandler; +/** + * Netty channel initializer for the test server. + * + *

      Configures the pipeline for HTTP/1.1 or HTTP/2 based on the server configuration, + * with optional TLS and ALPN support. + */ public class ServerInitializer extends ChannelInitializer { private final Http2ClientHandlers h2ClientHandlers; private final Http11ClientHandlers h11ClientHandlers; @@ -78,7 +91,7 @@ protected void initChannel(Channel ch) { } } - public static class Http2ClientHandlers { + public static final class Http2ClientHandlers { private final Http2ClientHandlerFactory factory; private final Map h2ClientHandlers = new ConcurrentHashMap<>(); @@ -97,7 +110,7 @@ public Http2ClientHandler get(ChannelHandlerContext ctx) { } } - public static class Http11ClientHandlers { + public static final class Http11ClientHandlers { private final Http11ClientHandlerFactory factory; private final Map h11ClientHandlers = new ConcurrentHashMap<>(); diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ChunkedResponseHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ChunkedResponseHttp11ClientHandler.java new file mode 100644 index 000000000..0047368bf --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ChunkedResponseHttp11ClientHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h1; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultHttpContent; +import io.netty.handler.codec.http.DefaultHttpResponse; +import io.netty.handler.codec.http.DefaultLastHttpContent; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * HTTP/1.1 handler that sends chunked transfer encoding response. + */ +public class ChunkedResponseHttp11ClientHandler implements Http11ClientHandler { + + private final List chunks; + + public ChunkedResponseHttp11ClientHandler(List chunks) { + this.chunks = chunks; + } + + @Override + public void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request) { + var response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); + response.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED); + response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); + ctx.write(response); + + for (String chunk : chunks) { + ctx.write(new DefaultHttpContent(Unpooled.wrappedBuffer(chunk.getBytes(StandardCharsets.UTF_8)))); + } + ctx.writeAndFlush(new DefaultLastHttpContent()); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ConnectionCloseHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ConnectionCloseHttp11ClientHandler.java new file mode 100644 index 000000000..d5abb9493 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ConnectionCloseHttp11ClientHandler.java @@ -0,0 +1,40 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h1; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import java.nio.charset.StandardCharsets; + +/** + * HTTP/1.1 handler that sends Connection: close header. + */ +public class ConnectionCloseHttp11ClientHandler implements Http11ClientHandler { + + private final String responseBody; + + public ConnectionCloseHttp11ClientHandler(String responseBody) { + this.responseBody = responseBody; + } + + @Override + public void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request) { + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + var response = new DefaultFullHttpResponse( + HttpVersion.HTTP_1_1, + HttpResponseStatus.OK, + Unpooled.wrappedBuffer(body)); + response.headers().set(HttpHeaderNames.CONTENT_LENGTH, body.length); + response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); + response.headers().set(HttpHeaderNames.CONNECTION, "close"); + ctx.writeAndFlush(response).addListener(f -> ctx.close()); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ConnectionTrackingHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ConnectionTrackingHttp11ClientHandler.java new file mode 100644 index 000000000..9b11cffb9 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ConnectionTrackingHttp11ClientHandler.java @@ -0,0 +1,67 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h1; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelId; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.LastHttpContent; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * HTTP/1.1 handler that tracks unique connections and request counts. + */ +public class ConnectionTrackingHttp11ClientHandler implements Http11ClientHandler { + + private final Http11ClientHandler delegate; + private final Set seenConnections = ConcurrentHashMap.newKeySet(); + private final AtomicInteger requestCount = new AtomicInteger(); + + public ConnectionTrackingHttp11ClientHandler(Http11ClientHandler delegate) { + this.delegate = delegate; + } + + public int connectionCount() { + return seenConnections.size(); + } + + public int requestCount() { + return requestCount.get(); + } + + @Override + public void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request) { + seenConnections.add(ctx.channel().id()); + requestCount.incrementAndGet(); + delegate.onFullRequest(ctx, request); + } + + @Override + public void onRequest(ChannelHandlerContext ctx, HttpRequest request) { + seenConnections.add(ctx.channel().id()); + requestCount.incrementAndGet(); + delegate.onRequest(ctx, request); + } + + @Override + public void onContent(ChannelHandlerContext ctx, HttpContent content) { + delegate.onContent(ctx, content); + } + + @Override + public void onLastContent(ChannelHandlerContext ctx, LastHttpContent content) { + delegate.onLastContent(ctx, content); + } + + @Override + public void onException(ChannelHandlerContext ctx, Throwable cause) { + delegate.onException(ctx, cause); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ContinueHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ContinueHttp11ClientHandler.java new file mode 100644 index 000000000..f97ca0245 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/ContinueHttp11ClientHandler.java @@ -0,0 +1,56 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h1; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.DefaultHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicReference; + +/** + * HTTP/1.1 handler that handles 100-continue correctly. + */ +public class ContinueHttp11ClientHandler implements Http11ClientHandler { + + private final String responseBody; + private final AtomicReference capturedBody = new AtomicReference<>(Unpooled.buffer()); + + public ContinueHttp11ClientHandler(String responseBody) { + this.responseBody = responseBody; + } + + public ByteBuf capturedBody() { + return capturedBody.get(); + } + + @Override + public void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request) { + // Check for Expect: 100-continue and send 100 Continue when seen + if (request.headers().contains(HttpHeaderNames.EXPECT, "100-continue", true)) { + ctx.writeAndFlush(new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE)); + } + + // Capture request body + capturedBody.get().writeBytes(request.content()); + + // Send final response + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + var response = new DefaultFullHttpResponse( + HttpVersion.HTTP_1_1, + HttpResponseStatus.OK, + Unpooled.wrappedBuffer(body)); + response.headers().set(HttpHeaderNames.CONTENT_LENGTH, body.length); + response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); + ctx.writeAndFlush(response); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/DelayedResponseHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/DelayedResponseHttp11ClientHandler.java new file mode 100644 index 000000000..450049231 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/DelayedResponseHttp11ClientHandler.java @@ -0,0 +1,52 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h1; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.LastHttpContent; +import java.util.concurrent.TimeUnit; + +/** + * HTTP/1.1 handler that delays response. + */ +public class DelayedResponseHttp11ClientHandler implements Http11ClientHandler { + + private final Http11ClientHandler delegate; + private final long delayMs; + + public DelayedResponseHttp11ClientHandler(Http11ClientHandler delegate, long delayMs) { + this.delegate = delegate; + this.delayMs = delayMs; + } + + @Override + public void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request) { + ctx.executor().schedule(() -> delegate.onFullRequest(ctx, request), delayMs, TimeUnit.MILLISECONDS); + } + + @Override + public void onRequest(ChannelHandlerContext ctx, HttpRequest request) { + delegate.onRequest(ctx, request); + } + + @Override + public void onContent(ChannelHandlerContext ctx, HttpContent content) { + delegate.onContent(ctx, content); + } + + @Override + public void onLastContent(ChannelHandlerContext ctx, LastHttpContent content) { + delegate.onLastContent(ctx, content); + } + + @Override + public void onException(ChannelHandlerContext ctx, Throwable cause) { + delegate.onException(ctx, cause); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/EmptyResponseHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/EmptyResponseHttp11ClientHandler.java new file mode 100644 index 000000000..fa8f7d969 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/EmptyResponseHttp11ClientHandler.java @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h1; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; + +/** + * HTTP/1.1 handler that sends empty response body. + */ +public class EmptyResponseHttp11ClientHandler implements Http11ClientHandler { + @Override + public void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request) { + var response = + new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NO_CONTENT, Unpooled.EMPTY_BUFFER); + response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0); + ctx.writeAndFlush(response); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/Http11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/Http11ClientHandler.java new file mode 100644 index 000000000..dab893d0e --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/Http11ClientHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h1; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.LastHttpContent; + +/** + * Handler interface for HTTP/1.1 requests in the test server. + */ +public interface Http11ClientHandler { + + /** + * Called when a complete HTTP request is received (headers + body aggregated). + * + * @param ctx the channel handler context + * @param request the complete HTTP request + */ + default void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request) {} + + /** + * Called when HTTP request headers are received (streaming mode). + * + * @param ctx the channel handler context + * @param request the HTTP request headers + */ + default void onRequest(ChannelHandlerContext ctx, HttpRequest request) {} + + /** + * Called when HTTP request body content is received (streaming mode). + * + * @param ctx the channel handler context + * @param content the HTTP content chunk + */ + default void onContent(ChannelHandlerContext ctx, HttpContent content) {} + + /** + * Called when the last HTTP request body content is received (streaming mode). + * + * @param ctx the channel handler context + * @param content the last HTTP content chunk + */ + default void onLastContent(ChannelHandlerContext ctx, LastHttpContent content) {} + + /** + * Called when an exception occurs during request processing. + * + * @param ctx the channel handler context + * @param cause the exception + */ + default void onException(ChannelHandlerContext ctx, Throwable cause) { + ctx.close(); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandlerFactory.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/Http11ClientHandlerFactory.java similarity index 82% rename from http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandlerFactory.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/Http11ClientHandlerFactory.java index 4e311187f..f45722174 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11ClientHandlerFactory.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/Http11ClientHandlerFactory.java @@ -3,12 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.it.server; +package software.amazon.smithy.java.http.client.it.server.h1; import io.netty.channel.ChannelHandlerContext; @FunctionalInterface public interface Http11ClientHandlerFactory { - Http11ClientHandler create(ChannelHandlerContext ctx); } diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11Handler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/Http11Handler.java similarity index 92% rename from http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11Handler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/Http11Handler.java index 25495a25f..fa9436af5 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http11Handler.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/Http11Handler.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.it.server; +package software.amazon.smithy.java.http.client.it.server.h1; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; @@ -11,6 +11,7 @@ import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.LastHttpContent; +import software.amazon.smithy.java.http.client.it.server.ServerInitializer; public class Http11Handler extends ChannelInboundHandlerAdapter { private final ServerInitializer.Http11ClientHandlers handlers; diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/LargeHeadersHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/LargeHeadersHttp11ClientHandler.java new file mode 100644 index 000000000..d5a3f4e06 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/LargeHeadersHttp11ClientHandler.java @@ -0,0 +1,50 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h1; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import java.nio.charset.StandardCharsets; + +/** + * HTTP/1.1 handler that sends response with large headers. + */ +public class LargeHeadersHttp11ClientHandler implements Http11ClientHandler { + + private final String body; + private final int headerCount; + private final int headerValueSize; + + public LargeHeadersHttp11ClientHandler(String body, int headerCount, int headerValueSize) { + this.body = body; + this.headerCount = headerCount; + this.headerValueSize = headerValueSize; + } + + @Override + public void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request) { + byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8); + var response = new DefaultFullHttpResponse( + HttpVersion.HTTP_1_1, + HttpResponseStatus.OK, + Unpooled.wrappedBuffer(bodyBytes)); + response.headers().set(HttpHeaderNames.CONTENT_LENGTH, bodyBytes.length); + response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); + + // Add large headers + String largeValue = "x".repeat(headerValueSize); + for (int i = 0; i < headerCount; i++) { + response.headers().set("x-large-header-" + i, largeValue); + } + + ctx.writeAndFlush(response); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/MultiplexingHttp11ClientHandler.java similarity index 96% rename from http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp11ClientHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/MultiplexingHttp11ClientHandler.java index bfcf9c94b..bb0744cda 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp11ClientHandler.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/MultiplexingHttp11ClientHandler.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.it.server; +package software.amazon.smithy.java.http.client.it.server.h1; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/RequestCapturingHttp11ClientHandler.java similarity index 94% rename from http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp11ClientHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/RequestCapturingHttp11ClientHandler.java index 7b453b68f..6705e1cb6 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp11ClientHandler.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/RequestCapturingHttp11ClientHandler.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.it.server; +package software.amazon.smithy.java.http.client.it.server.h1; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; @@ -58,7 +58,7 @@ private void captureHeaders(HttpRequest request) { private void captureBody(ByteBuf content) { var bytes = new byte[content.readableBytes()]; - content.readBytes(bytes); + content.getBytes(content.readerIndex(), bytes); // Don't consume the buffer try { capturedBody.write(bytes); } catch (IOException e) { diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/StatusCodeHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/StatusCodeHttp11ClientHandler.java new file mode 100644 index 000000000..ac7f3b29c --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/StatusCodeHttp11ClientHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h1; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; + +/** + * HTTP/1.1 handler that sends configurable status code. + */ +public class StatusCodeHttp11ClientHandler implements Http11ClientHandler { + + private final int statusCode; + + public StatusCodeHttp11ClientHandler(int statusCode) { + this.statusCode = statusCode; + } + + @Override + public void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request) { + var status = HttpResponseStatus.valueOf(statusCode); + var response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.EMPTY_BUFFER); + + // Some status codes must not have body + if (statusCode != 204 && statusCode != 304) { + response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0); + } + + ctx.writeAndFlush(response); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/TextResponseHttp11ClientHandler.java similarity index 78% rename from http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp11ClientHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/TextResponseHttp11ClientHandler.java index 26e0f5de3..f2e40651f 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp11ClientHandler.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/TextResponseHttp11ClientHandler.java @@ -3,21 +3,22 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.it.server; +package software.amazon.smithy.java.http.client.it.server.h1; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; -import io.netty.handler.codec.http.LastHttpContent; import io.netty.util.CharsetUtil; +/** + * HTTP/1.1 handler that sends a simple text response. + */ public class TextResponseHttp11ClientHandler implements Http11ClientHandler { private final String message; @@ -35,21 +36,6 @@ public void onRequest(ChannelHandlerContext ctx, HttpRequest request) { sendResponse(ctx); } - @Override - public void onContent(ChannelHandlerContext ctx, HttpContent content) { - - } - - @Override - public void onLastContent(ChannelHandlerContext ctx, LastHttpContent content) { - - } - - @Override - public void onException(ChannelHandlerContext ctx, Throwable cause) { - - } - private void sendResponse(ChannelHandlerContext ctx) { var content = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8); var response = new DefaultFullHttpResponse( @@ -62,5 +48,4 @@ private void sendResponse(ChannelHandlerContext ctx) { headers.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); ctx.writeAndFlush(response); } - } diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/TrailerResponseHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/TrailerResponseHttp11ClientHandler.java new file mode 100644 index 000000000..0ca735689 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/TrailerResponseHttp11ClientHandler.java @@ -0,0 +1,52 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h1; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultHttpContent; +import io.netty.handler.codec.http.DefaultHttpResponse; +import io.netty.handler.codec.http.DefaultLastHttpContent; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +/** + * HTTP/1.1 handler that sends chunked response with trailers. + */ +public class TrailerResponseHttp11ClientHandler implements Http11ClientHandler { + + private final String body; + private final Map trailers; + + public TrailerResponseHttp11ClientHandler(String body, Map trailers) { + this.body = body; + this.trailers = trailers; + } + + @Override + public void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request) { + var response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); + response.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED); + response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); + response.headers().set("trailer", String.join(", ", trailers.keySet())); + ctx.write(response); + + // Send body chunk + ctx.write(new DefaultHttpContent(Unpooled.wrappedBuffer(body.getBytes(StandardCharsets.UTF_8)))); + + // Send last chunk with trailers + var lastContent = new DefaultLastHttpContent(); + for (var entry : trailers.entrySet()) { + lastContent.trailingHeaders().set(entry.getKey(), entry.getValue()); + } + ctx.writeAndFlush(lastContent); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/ConnectionTrackingHttp2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/ConnectionTrackingHttp2ClientHandler.java new file mode 100644 index 000000000..18910fa00 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/ConnectionTrackingHttp2ClientHandler.java @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h2; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelId; +import io.netty.handler.codec.http2.Http2DataFrame; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * HTTP/2 handler that tracks unique connections and request counts. + */ +public class ConnectionTrackingHttp2ClientHandler implements Http2ClientHandler { + + private final Http2ClientHandler delegate; + private final Set seenConnections = ConcurrentHashMap.newKeySet(); + private final AtomicInteger requestCount = new AtomicInteger(); + + public ConnectionTrackingHttp2ClientHandler(Http2ClientHandler delegate) { + this.delegate = delegate; + } + + public int connectionCount() { + return seenConnections.size(); + } + + public int requestCount() { + return requestCount.get(); + } + + @Override + public void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame) { + // parent() gives us the connection channel (stream channels have the connection as parent) + seenConnections.add(ctx.channel().parent().id()); + requestCount.incrementAndGet(); + delegate.onHeadersFrame(ctx, frame); + } + + @Override + public void onDataFrame(ChannelHandlerContext ctx, Http2DataFrame frame) { + delegate.onDataFrame(ctx, frame); + } + + @Override + public void onException(ChannelHandlerContext ctx, Throwable cause) { + delegate.onException(ctx, cause); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/DelayedResponseHttp2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/DelayedResponseHttp2ClientHandler.java new file mode 100644 index 000000000..caea26b85 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/DelayedResponseHttp2ClientHandler.java @@ -0,0 +1,58 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h2; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * HTTP/2 handler that delays response and tracks concurrent streams. + */ +public class DelayedResponseHttp2ClientHandler implements Http2ClientHandler { + + private final String responseBody; + private final long delayMs; + private final AtomicInteger concurrentStreams = new AtomicInteger(); + private volatile int maxObservedConcurrent = 0; + + public DelayedResponseHttp2ClientHandler(String responseBody, long delayMs) { + this.responseBody = responseBody; + this.delayMs = delayMs; + } + + public int maxObservedConcurrent() { + return maxObservedConcurrent; + } + + @Override + public void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame) { + int current = concurrentStreams.incrementAndGet(); + synchronized (this) { + if (current > maxObservedConcurrent) { + maxObservedConcurrent = current; + } + } + + ctx.executor().schedule(() -> { + concurrentStreams.decrementAndGet(); + + var headers = new DefaultHttp2Headers(); + headers.status("200"); + headers.set("content-type", "text/plain"); + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + headers.setInt("content-length", body.length); + ctx.write(new DefaultHttp2HeadersFrame(headers)); + ctx.writeAndFlush(new DefaultHttp2DataFrame(Unpooled.wrappedBuffer(body), true)); + }, delayMs, TimeUnit.MILLISECONDS); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/GoawayAfterFirstRequestHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/GoawayAfterFirstRequestHandler.java new file mode 100644 index 000000000..6b92bddd1 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/GoawayAfterFirstRequestHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h2; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelId; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; +import io.netty.handler.codec.http2.DefaultHttp2GoAwayFrame; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; +import io.netty.handler.codec.http2.Http2Error; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import java.nio.charset.StandardCharsets; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * HTTP/2 handler that sends GOAWAY after first request on each connection. + */ +public class GoawayAfterFirstRequestHandler implements Http2ClientHandler { + + private final Set seenConnections = ConcurrentHashMap.newKeySet(); + private final String responseBody; + + public GoawayAfterFirstRequestHandler(String responseBody) { + this.responseBody = responseBody; + } + + public int connectionCount() { + return seenConnections.size(); + } + + @Override + public void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame) { + var connectionChannel = ctx.channel().parent(); + boolean firstRequest = seenConnections.add(connectionChannel.id()); + + // Send normal response + var headers = new DefaultHttp2Headers(); + headers.status("200"); + headers.set("content-type", "text/plain"); + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + headers.setInt("content-length", body.length); + ctx.write(new DefaultHttp2HeadersFrame(headers)); + ctx.writeAndFlush(new DefaultHttp2DataFrame(Unpooled.wrappedBuffer(body), true)); + + // Send GOAWAY after first request (with delay to let response complete) + if (firstRequest) { + ctx.executor() + .schedule( + () -> connectionChannel.writeAndFlush(new DefaultHttp2GoAwayFrame(Http2Error.NO_ERROR)), + 100, + java.util.concurrent.TimeUnit.MILLISECONDS); + } + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/Http2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/Http2ClientHandler.java new file mode 100644 index 000000000..7b65a3497 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/Http2ClientHandler.java @@ -0,0 +1,42 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h2; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http2.Http2DataFrame; +import io.netty.handler.codec.http2.Http2HeadersFrame; + +/** + * Handler interface for HTTP/2 requests in the test server. + */ +public interface Http2ClientHandler { + + /** + * Called when HTTP/2 HEADERS frame is received. + * + * @param ctx the channel handler context + * @param frame the headers frame + */ + default void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame) {} + + /** + * Called when HTTP/2 DATA frame is received. + * + * @param ctx the channel handler context + * @param frame the data frame + */ + default void onDataFrame(ChannelHandlerContext ctx, Http2DataFrame frame) {} + + /** + * Called when an exception occurs during request processing. + * + * @param ctx the channel handler context + * @param cause the exception + */ + default void onException(ChannelHandlerContext ctx, Throwable cause) { + ctx.close(); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandlerFactory.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/Http2ClientHandlerFactory.java similarity index 82% rename from http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandlerFactory.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/Http2ClientHandlerFactory.java index e8257869c..6576b03cf 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ClientHandlerFactory.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/Http2ClientHandlerFactory.java @@ -3,12 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.it.server; +package software.amazon.smithy.java.http.client.it.server.h2; import io.netty.channel.ChannelHandlerContext; @FunctionalInterface public interface Http2ClientHandlerFactory { - Http2ClientHandler create(ChannelHandlerContext ctx); } diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/Http2ConnectionFrameHandler.java similarity index 93% rename from http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/Http2ConnectionFrameHandler.java index 60d5f988a..f5342ae9b 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2ConnectionFrameHandler.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/Http2ConnectionFrameHandler.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.it.server; +package software.amazon.smithy.java.http.client.it.server.h2; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; @@ -11,6 +11,7 @@ import io.netty.handler.codec.http2.Http2PingFrame; import io.netty.handler.codec.http2.Http2SettingsAckFrame; import io.netty.handler.codec.http2.Http2SettingsFrame; +import software.amazon.smithy.java.http.client.it.server.NettyTestLogger; public class Http2ConnectionFrameHandler extends ChannelInboundHandlerAdapter { private static final NettyTestLogger LOGGER = NettyTestLogger.getLogger(Http2ConnectionFrameHandler.class); diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2StreamFrameHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/Http2StreamFrameHandler.java similarity index 88% rename from http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2StreamFrameHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/Http2StreamFrameHandler.java index 7d4e2bc1d..d1d3aded3 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/Http2StreamFrameHandler.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/Http2StreamFrameHandler.java @@ -3,15 +3,17 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.it.server; +package software.amazon.smithy.java.http.client.it.server.h2; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http2.Http2DataFrame; import io.netty.handler.codec.http2.Http2Frame; import io.netty.handler.codec.http2.Http2HeadersFrame; +import software.amazon.smithy.java.http.client.it.server.NettyTestLogger; +import software.amazon.smithy.java.http.client.it.server.ServerInitializer; -class Http2StreamFrameHandler extends SimpleChannelInboundHandler { +public class Http2StreamFrameHandler extends SimpleChannelInboundHandler { private static final NettyTestLogger LOGGER = NettyTestLogger.getLogger(Http2StreamFrameHandler.class); private final ServerInitializer.Http2ClientHandlers handlers; diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/LargeResponseHttp2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/LargeResponseHttp2ClientHandler.java new file mode 100644 index 000000000..98ab21eeb --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/LargeResponseHttp2ClientHandler.java @@ -0,0 +1,58 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h2; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; +import io.netty.handler.codec.http2.Http2HeadersFrame; + +/** + * HTTP/2 handler that sends a large response body in chunks. + */ +public class LargeResponseHttp2ClientHandler implements Http2ClientHandler { + + private final int responseSize; + private final int chunkSize; + + public LargeResponseHttp2ClientHandler(int responseSize, int chunkSize) { + this.responseSize = responseSize; + this.chunkSize = chunkSize; + } + + @Override + public void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame) { + // Send response headers + var headers = new DefaultHttp2Headers(); + headers.status("200"); + headers.set("content-type", "application/octet-stream"); + headers.setInt("content-length", responseSize); + ctx.write(new DefaultHttp2HeadersFrame(headers)); + + // Send body in chunks + int remaining = responseSize; + while (remaining > 0) { + int size = Math.min(chunkSize, remaining); + byte[] chunk = new byte[size]; + // Fill with predictable pattern for verification + for (int i = 0; i < size; i++) { + chunk[i] = (byte) ((responseSize - remaining + i) & 0xFF); + } + boolean endStream = (remaining - size) == 0; + ctx.write(new DefaultHttp2DataFrame(Unpooled.wrappedBuffer(chunk), endStream)); + remaining -= size; + } + ctx.flush(); + } + + @Override + public void onException(ChannelHandlerContext ctx, Throwable cause) { + cause.printStackTrace(); + ctx.close(); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/MultiplexingHttp2ClientHandler.java similarity index 94% rename from http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp2ClientHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/MultiplexingHttp2ClientHandler.java index 3e1293e96..7767ea1ca 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/MultiplexingHttp2ClientHandler.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/MultiplexingHttp2ClientHandler.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.it.server; +package software.amazon.smithy.java.http.client.it.server.h2; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http2.Http2DataFrame; diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/PartialResponseHttp2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/PartialResponseHttp2ClientHandler.java new file mode 100644 index 000000000..99309990b --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/PartialResponseHttp2ClientHandler.java @@ -0,0 +1,35 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h2; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import java.nio.charset.StandardCharsets; + +/** + * HTTP/2 handler that sends partial response then closes connection. + */ +public class PartialResponseHttp2ClientHandler implements Http2ClientHandler { + + @Override + public void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame) { + var headers = new DefaultHttp2Headers(); + headers.status("200"); + headers.setInt("content-length", 1000); // Claim 1000 bytes + ctx.write(new DefaultHttp2HeadersFrame(headers)); + + // Send only partial data (100 bytes), don't set endStream + byte[] partial = "partial".repeat(14).getBytes(StandardCharsets.UTF_8); + ctx.writeAndFlush(new DefaultHttp2DataFrame(Unpooled.wrappedBuffer(partial), false)); + + // Close connection abruptly + ctx.channel().parent().close(); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/RequestCapturingHttp2ClientHandler.java similarity index 97% rename from http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp2ClientHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/RequestCapturingHttp2ClientHandler.java index 5d5288011..a1ae2a391 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/RequestCapturingHttp2ClientHandler.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/RequestCapturingHttp2ClientHandler.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.it.server; +package software.amazon.smithy.java.http.client.it.server.h2; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http2.Http2DataFrame; diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/RstStreamHttp2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/RstStreamHttp2ClientHandler.java new file mode 100644 index 000000000..19cbc2cc5 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/RstStreamHttp2ClientHandler.java @@ -0,0 +1,29 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h2; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http2.DefaultHttp2ResetFrame; +import io.netty.handler.codec.http2.Http2Error; +import io.netty.handler.codec.http2.Http2HeadersFrame; + +/** + * HTTP/2 handler that sends RST_STREAM after receiving headers. + */ +public class RstStreamHttp2ClientHandler implements Http2ClientHandler { + + private final Http2Error errorCode; + + public RstStreamHttp2ClientHandler(Http2Error errorCode) { + this.errorCode = errorCode; + } + + @Override + public void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame) { + // Send RST_STREAM instead of response + ctx.writeAndFlush(new DefaultHttp2ResetFrame(errorCode)); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/StreamingResponseHttp2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/StreamingResponseHttp2ClientHandler.java new file mode 100644 index 000000000..8cf1468d0 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/StreamingResponseHttp2ClientHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h2; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * HTTP/2 handler that sends response in chunks with delays. + */ +public class StreamingResponseHttp2ClientHandler implements Http2ClientHandler { + + private final List chunks; + private final long delayBetweenChunksMs; + + public StreamingResponseHttp2ClientHandler(List chunks, long delayBetweenChunksMs) { + this.chunks = chunks; + this.delayBetweenChunksMs = delayBetweenChunksMs; + } + + @Override + public void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame) { + var headers = new DefaultHttp2Headers(); + headers.status("200"); + headers.set("content-type", "text/plain"); + ctx.writeAndFlush(new DefaultHttp2HeadersFrame(headers)); + + // Send chunks with delays + for (int i = 0; i < chunks.size(); i++) { + final int index = i; + final boolean isLast = (i == chunks.size() - 1); + ctx.executor().schedule(() -> { + byte[] data = chunks.get(index).getBytes(StandardCharsets.UTF_8); + ctx.writeAndFlush(new DefaultHttp2DataFrame(Unpooled.wrappedBuffer(data), isLast)); + }, delayBetweenChunksMs * i, TimeUnit.MILLISECONDS); + } + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/TextResponseHttp2ClientHandler.java similarity index 91% rename from http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java rename to http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/TextResponseHttp2ClientHandler.java index b152e2ad4..0c646fa8d 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/TextResponseHttp2ClientHandler.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/TextResponseHttp2ClientHandler.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.it.server; +package software.amazon.smithy.java.http.client.it.server.h2; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; @@ -13,6 +13,7 @@ import io.netty.handler.codec.http2.Http2DataFrame; import io.netty.handler.codec.http2.Http2HeadersFrame; import io.netty.util.CharsetUtil; +import software.amazon.smithy.java.http.client.it.server.NettyTestLogger; public class TextResponseHttp2ClientHandler implements Http2ClientHandler { private static final NettyTestLogger LOGGER = NettyTestLogger.getLogger(TextResponseHttp2ClientHandler.class); @@ -41,9 +42,4 @@ public void onDataFrame(ChannelHandlerContext ctx, Http2DataFrame frame) { ctx.writeAndFlush(new DefaultHttp2DataFrame(true)); } } - - @Override - public void onException(ChannelHandlerContext ctx, Throwable cause) { - - } } diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/TrailerResponseHttp2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/TrailerResponseHttp2ClientHandler.java new file mode 100644 index 000000000..8ecbb646a --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/TrailerResponseHttp2ClientHandler.java @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h2; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import io.netty.util.CharsetUtil; +import java.util.Map; + +/** + * HTTP/2 handler that sends response with trailer headers. + */ +public class TrailerResponseHttp2ClientHandler implements Http2ClientHandler { + + private final String body; + private final Map trailers; + + public TrailerResponseHttp2ClientHandler(String body, Map trailers) { + this.body = body; + this.trailers = trailers; + } + + @Override + public void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame) { + // Send response headers (not end of stream) + var responseHeaders = new DefaultHttp2Headers(); + responseHeaders.status("200"); + responseHeaders.set("content-type", "text/plain"); + ctx.write(new DefaultHttp2HeadersFrame(responseHeaders, false)); + + // Send body (not end of stream) + var content = Unpooled.copiedBuffer(body, CharsetUtil.UTF_8); + ctx.write(new DefaultHttp2DataFrame(content, false)); + + // Send trailers (end of stream) + var trailerHeaders = new DefaultHttp2Headers(); + for (var entry : trailers.entrySet()) { + trailerHeaders.set(entry.getKey(), entry.getValue()); + } + ctx.writeAndFlush(new DefaultHttp2HeadersFrame(trailerHeaders, true)); + } +} diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java index 3058c34ab..64bde0c8b 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java @@ -11,11 +11,14 @@ import java.time.Duration; import java.util.concurrent.TimeUnit; import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.io.entity.ByteArrayEntity; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.util.Timeout; import org.openjdk.jmh.annotations.AuxCounters; @@ -200,4 +203,20 @@ public void smithyPost(Counter counter) throws InterruptedException { counter.logErrors("Smithy H1 POST"); } + + @Benchmark + @Threads(1) + public void apachePost(Counter counter) throws InterruptedException { + var target = BenchmarkSupport.H1_URL + "/post"; + + BenchmarkSupport.runBenchmark(concurrency, 1000, (String url) -> { + var post = new HttpPost(url); + post.setEntity(new ByteArrayEntity(BenchmarkSupport.POST_PAYLOAD, ContentType.APPLICATION_OCTET_STREAM)); + try (var response = apacheClient.execute(post)) { + EntityUtils.consume(response.getEntity()); + } + }, target, counter); + + counter.logErrors("Apache H1 POST"); + } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java index b151335e5..443c75d46 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java @@ -138,6 +138,11 @@ public HttpHeaders responseHeaders() throws IOException { } } + @Override + public HttpHeaders responseTrailerHeaders() { + return delegate.responseTrailerHeaders(); + } + @Override public int responseStatusCode() throws IOException { try { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java index 9536770c5..b652b999e 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java @@ -218,18 +218,33 @@ public HttpConnection acquire(Route route) throws IOException { private HttpConnection acquireH1(Route route) throws IOException { int maxConns = getMaxConnectionsForRoute(route); - // Quick check: try to reuse a pooled connection - H1ConnectionManager.PooledConnection pooled = h1Manager.tryAcquire(route, maxConns); + // Try to get a permit without blocking + if (connectionPermits.tryAcquire()) { + // Got a permit, so now try to reuse a pooled connection first + H1ConnectionManager.PooledConnection pooled = h1Manager.tryAcquire(route, maxConns); + if (pooled != null) { + notifyAcquire(pooled.connection(), true); + return pooled.connection(); + } else { + // No pooled connection, but we have a permit to create one. + return createH1Connection(route); + } + } + + // No permit available immediately. Block on global capacity with timeout. + acquirePermit(); + // Re-check pool after acquiring the permit, since a connection may have been released while waiting. + H1ConnectionManager.PooledConnection pooled = h1Manager.tryAcquire(route, maxConns); if (pooled != null) { notifyAcquire(pooled.connection(), true); return pooled.connection(); } - // No valid pooled connection available, so block on global capacity with timeout. - acquirePermit(); + return createH1Connection(route); + } - // Create new HTTP/1.1 connection + private HttpConnection createH1Connection(Route route) throws IOException { HttpConnection conn = null; boolean success = false; try { @@ -324,9 +339,10 @@ public void release(HttpConnection connection) { return; } - // H1 connection handling if (!h1Manager.release(route, connection, closed)) { closeAndReleasePermit(connection, CloseReason.POOL_FULL); + } else { + connectionPermits.release(); } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java index 77f9e9762..42846117a 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java @@ -62,6 +62,44 @@ boolean tryAcquire(int bytes, long timeoutMs) throws InterruptedException { } } + /** + * Try to acquire up to the requested bytes from the window. + * + *

      This method acquires as many bytes as available (up to the requested amount), + * waiting only if the window is completely empty. + * + * @param maxBytes maximum number of bytes to acquire + * @param timeoutMs maximum time to wait in milliseconds (only if window is empty) + * @return number of bytes acquired (0 if timeout expired with empty window) + * @throws InterruptedException if interrupted while waiting + */ + int tryAcquireUpTo(int maxBytes, long timeoutMs) throws InterruptedException { + lock.lock(); + try { + // Fast path: window has capacity + if (window > 0) { + int acquired = (int) Math.min(window, maxBytes); + window -= acquired; + return acquired; + } + + // Slow path: wait for any capacity + long remainingNs = TimeUnit.MILLISECONDS.toNanos(timeoutMs); + while (window <= 0) { + if (remainingNs <= 0) { + return 0; + } + remainingNs = available.awaitNanos(remainingNs); + } + + int acquired = (int) Math.min(window, maxBytes); + window -= acquired; + return acquired; + } finally { + lock.unlock(); + } + } + /** * Release bytes back to the window. * diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index c4eb6a209..d916f2fd9 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -64,6 +64,9 @@ public final class H2Exchange implements HttpExchange { private static final InternalLogger LOGGER = InternalLogger.getLogger(H2Exchange.class); + // Max frames to acquire flow control for in a single batch (64 frames = 1MB at default 16KB frame size) + private static final int FLOW_CONTROL_BATCH_FRAMES = 64; + // VarHandle for atomic inWorkQueue CAS private static final VarHandle IN_WORK_QUEUE_HANDLE; @@ -905,13 +908,16 @@ void updateStreamSendWindow(int increment) throws H2Exception { } /** - * Write DATA frame for request body with flow control. + * Write DATA frames for request body with flow control. + * + *

      Uses batched flow control acquisition to prevent connection window starvation under high concurrency. + * Acquires up to {@value #FLOW_CONTROL_BATCH_FRAMES} frames worth of window at a time. * - *

      Uses the SPSC (single-producer, single-consumer) pattern: + *

      Flow: *

        - *
      1. VT acquires flow control (blocks naturally via FlowControlWindow monitor)
      2. - *
      3. VT copies data to pooled buffer and adds to pendingWrites queue
      4. - *
      5. VT signals writer thread via muxer
      6. + *
      7. VT acquires stream and connection flow control in batches
      8. + *
      9. VT copies data to pooled buffers and adds to pendingWrites queue
      10. + *
      11. VT signals writer thread once after all frames are queued
      12. *
      13. Writer thread drains pendingWrites and writes frames
      14. *
      * @@ -921,65 +927,74 @@ void writeData(byte[] data, int offset, int length, boolean endStream) throws IO // If trailers are set and this is the last data, don't set END_STREAM on DATA frame // - trailers will carry END_STREAM instead boolean hasTrailers = requestTrailers != null; + int maxFrameSize = muxer.getRemoteMaxFrameSize(); while (length > 0) { - // Determine how much we can send based on frame size limit - int maxFrameSize = muxer.getRemoteMaxFrameSize(); - int toSend = Math.min(length, maxFrameSize); - - // Acquire stream-level flow control (uses tick-based timeout) + // Acquire as much stream-level flow control as we can (up to remaining length) + int batchSize = Math.min(length, maxFrameSize * FLOW_CONTROL_BATCH_FRAMES); + int streamAcquired; try { - if (!sendWindow.tryAcquire(toSend, writeTimeoutMs)) { - throw new SocketTimeoutException( - "Write timed out after " + writeTimeoutMs + "ms waiting for stream flow control window"); + streamAcquired = sendWindow.tryAcquireUpTo(batchSize, writeTimeoutMs); + if (streamAcquired == 0) { + throw new SocketTimeoutException(String.format( + "Write timed out after %dms waiting for stream flow control window", + writeTimeoutMs)); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException("Interrupted waiting for stream flow control window", e); } - // Acquire connection-level flow control + // Acquire connection-level flow control for what we got from stream + int connAcquired; try { - muxer.acquireConnectionWindow(toSend, writeTimeoutMs); + connAcquired = muxer.acquireConnectionWindowUpTo(streamAcquired, writeTimeoutMs); + if (connAcquired == 0) { + sendWindow.release(streamAcquired); + throw new SocketTimeoutException(String.format( + "Write timed out after %dms waiting for connection flow control window", + writeTimeoutMs)); + } + // Release excess stream permits if connection gave us less + if (connAcquired < streamAcquired) { + sendWindow.release(streamAcquired - connAcquired); + } } catch (InterruptedException e) { - // Release stream permits we acquired - sendWindow.release(toSend); + sendWindow.release(streamAcquired); Thread.currentThread().interrupt(); throw new IOException("Interrupted waiting for connection flow control window", e); } catch (SocketTimeoutException e) { - // Release stream permits we acquired - sendWindow.release(toSend); + sendWindow.release(streamAcquired); throw e; } - boolean isLastChunk = (toSend == length); - // Only set END_STREAM on DATA if this is the last chunk AND no trailers - int flags = (endStream && isLastChunk && !hasTrailers) ? FLAG_END_STREAM : 0; - - // Copy data to pooled buffer (caller may reuse their buffer) - byte[] buf = muxer.borrowBuffer(toSend); - System.arraycopy(data, offset, buf, 0, toSend); + // Write frames using the acquired window + int batchRemaining = connAcquired; + while (batchRemaining > 0 && length > 0) { + int toSend = Math.min(Math.min(length, maxFrameSize), batchRemaining); + boolean isLastChunk = (toSend == length); + int flags = (endStream && isLastChunk && !hasTrailers) ? FLAG_END_STREAM : 0; + byte[] buf = muxer.borrowBuffer(toSend); + System.arraycopy(data, offset, buf, 0, toSend); - // Add to pendingWrites queue (lock-free concurrent queue) - PendingWrite pw = new PendingWrite(); - pw.init(buf, 0, toSend, flags); - pendingWrites.add(pw); + pendingWrites.add(new PendingWrite().init(buf, 0, toSend, flags)); - // Signal writer thread if not already in work queue (atomic CAS to avoid races) - if (IN_WORK_QUEUE_HANDLE.compareAndSet(this, false, true)) { - muxer.signalDataReady(this); + offset += toSend; + length -= toSend; + batchRemaining -= toSend; } + } - offset += toSend; - length -= toSend; + // Signal writer thread once after all data is queued + if (IN_WORK_QUEUE_HANDLE.compareAndSet(this, false, true)) { + muxer.signalDataReady(this); } if (endStream) { if (hasTrailers) { - // Send trailers with END_STREAM muxer.queueTrailers(streamId, requestTrailers); } - state.markEndStreamSent(); // Atomically sets flag and updates stream state + state.markEndStreamSent(); } } @@ -989,22 +1004,20 @@ void writeData(byte[] data, int offset, int length, boolean endStream) throws IO *

      Uses the same pendingWrites queue as writeData() to ensure proper ordering. * This prevents END_STREAM from being sent before pending DATA frames. */ - void sendEndStream() throws IOException { + void sendEndStream() { if (!state.isEndStreamSent()) { if (requestTrailers != null) { muxer.queueTrailers(streamId, requestTrailers); } else { // Use pendingWrites queue (same as writeData) to ensure ordering - PendingWrite pw = new PendingWrite(); - pw.init(H2Constants.EMPTY_BYTES, 0, 0, FLAG_END_STREAM); - pendingWrites.add(pw); + pendingWrites.add(new PendingWrite().init(H2Constants.EMPTY_BYTES, 0, 0, FLAG_END_STREAM)); // Signal writer thread if (IN_WORK_QUEUE_HANDLE.compareAndSet(this, false, true)) { muxer.signalDataReady(this); } } - state.markEndStreamSent(); // Atomically sets flag and updates stream state + state.markEndStreamSent(); } } @@ -1020,7 +1033,7 @@ private void updateStreamRecvWindow(int bytesConsumed) throws IOException { streamRecvWindow -= bytesConsumed; if (streamRecvWindow < initialWindowSize / H2Constants.WINDOW_UPDATE_THRESHOLD_DIVISOR) { int increment = initialWindowSize - streamRecvWindow; - // Queue stream-level WINDOW_UPDATE - writer thread will send it + // Queue stream-level WINDOW_UPDATE. Writer thread will send it. muxer.queueControlFrame(streamId, H2Muxer.ControlFrameType.WINDOW_UPDATE, increment, writeTimeoutMs); streamRecvWindow += increment; } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java index 094ef8ce2..9427b3d57 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java @@ -291,12 +291,15 @@ void onGoaway(int lastStreamId, int errorCode) { // ==================== FLOW CONTROL ==================== - void acquireConnectionWindow(int requestedBytes, long timeoutMs) - throws SocketTimeoutException, InterruptedException { - if (!connectionSendWindow.tryAcquire(requestedBytes, timeoutMs)) { - throw new SocketTimeoutException( - "Write timed out after " + timeoutMs + "ms waiting for connection flow control window"); - } + /** + * Acquire up to the requested bytes from the connection flow control window. + * + * @param maxBytes maximum bytes to acquire + * @param timeoutMs timeout if window is empty + * @return bytes acquired (0 if timeout) + */ + int acquireConnectionWindowUpTo(int maxBytes, long timeoutMs) throws SocketTimeoutException, InterruptedException { + return connectionSendWindow.tryAcquireUpTo(maxBytes, timeoutMs); } void releaseConnectionWindow(int bytes) { @@ -355,10 +358,6 @@ void queueTrailers(int streamId, HttpHeaders trailers) { enqueue(new H2MuxerWorkItem.WriteTrailers(streamId, trailers)); } - void queueData(int streamId, byte[] data, int offset, int length, int flags) { - enqueue(new H2MuxerWorkItem.WriteData(streamId, data, offset, length, flags)); - } - /** * Submit a HEADERS frame for encoding and writing. * Always succeeds (CLQ is unbounded, bounded by stream slots). @@ -523,8 +522,6 @@ private void workerLoop() { processBatch(batch); } - dataWorkPending.set(false); - boolean processedData = false; H2Exchange exchange; while ((exchange = dataWorkQueue.poll()) != null) { @@ -532,6 +529,10 @@ private void workerLoop() { processedData = true; } + // Reset flag only after draining to avoid race where VT signals while we're still processing, + // causing extra wake-ups and flushes + dataWorkPending.set(false); + if (processedData) { try { frameCodec.flush(); @@ -544,6 +545,8 @@ private void workerLoop() { // Check for timeouts periodically using tick-based system long now = System.currentTimeMillis(); if (now - lastTimeoutCheck >= TIMEOUT_POLL_INTERVAL_MS) { + // Single-writer (muxer thread) / multi-reader pattern. Only this thread increments. + @SuppressWarnings("NonAtomicOperationOnVolatileField") int tick = ++timeoutTick; checkReadTimeouts(tick); checkWriteTimeouts(tick); @@ -566,8 +569,6 @@ private void workerLoop() { } private void processExchangePendingWrites(H2Exchange exchange) { - exchange.inWorkQueue = false; - int streamId = exchange.getStreamId(); PendingWrite pw; while ((pw = exchange.pendingWrites.poll()) != null) { @@ -589,6 +590,10 @@ private void processExchangePendingWrites(H2Exchange exchange) { pw.reset(); } + // Reset inWorkQueue only after draining to avoid race where VT adds writes + // and re-enqueues while still processing. + exchange.inWorkQueue = false; + if (!exchange.pendingWrites.isEmpty() && !exchange.inWorkQueue) { exchange.inWorkQueue = true; dataWorkQueue.offer(exchange); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamState.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamState.java index 497bdfd6d..9cb69864d 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamState.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamState.java @@ -89,6 +89,7 @@ final class H2StreamState { } // Initial: SS_IDLE, RS_WAITING, no flags, status=0 + @SuppressWarnings("FieldMayBeFinal") // it's mutated with a VarHandle private volatile int packedState = (SS_IDLE << SHIFT_STREAM_STATE) | (RS_WAITING << SHIFT_READ_STATE); /** diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/PendingWrite.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/PendingWrite.java index d78c8edfc..70cdeac60 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/PendingWrite.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/PendingWrite.java @@ -41,11 +41,12 @@ final class PendingWrite { * @param length length to write * @param flags frame flags (see {@link H2Constants#FLAG_END_STREAM}) */ - void init(byte[] data, int offset, int length, int flags) { + PendingWrite init(byte[] data, int offset, int length, int flags) { this.data = data; this.offset = offset; this.length = length; this.flags = flags; + return this; } /** diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/FlowControlWindowTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/FlowControlWindowTest.java new file mode 100644 index 000000000..0d21b1e01 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/FlowControlWindowTest.java @@ -0,0 +1,126 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class FlowControlWindowTest { + + @Test + void initialWindowIsAvailable() { + var window = new FlowControlWindow(65535); + + assertEquals(65535, window.available()); + } + + @Test + void tryAcquireReducesWindow() throws Exception { + var window = new FlowControlWindow(1000); + boolean acquired = window.tryAcquire(400, 100); + + assertTrue(acquired); + assertEquals(600, window.available()); + } + + @Test + void tryAcquireBlocksWhenInsufficient() throws Exception { + var window = new FlowControlWindow(100); + // Try to acquire more than available with short timeout + boolean acquired = window.tryAcquire(200, 50); + + assertFalse(acquired, "Should timeout when insufficient"); + } + + @Test + void releaseIncreasesWindow() { + var window = new FlowControlWindow(1000); + window.release(500); + + assertEquals(1500, window.available()); + } + + @Test + void releaseWakesWaitingThread() throws Exception { + var window = new FlowControlWindow(100); + + Thread acquirer = Thread.startVirtualThread(() -> { + try { + window.tryAcquire(200, 5000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + + Thread.sleep(50); // Let acquirer start waiting + window.release(200); // Release enough + acquirer.join(1000); + + assertEquals(100, window.available()); // 100 + 200 - 200 = 100 + } + + @Test + void adjustIncreasesWindow() { + var window = new FlowControlWindow(1000); + window.adjust(500); + + assertEquals(1500, window.available()); + } + + @Test + void adjustDecreasesWindow() { + var window = new FlowControlWindow(1000); + window.adjust(-300); + + assertEquals(700, window.available()); + } + + @Test + void adjustCanMakeWindowNegative() { + var window = new FlowControlWindow(100); + window.adjust(-200); + + assertEquals(-100, window.available()); + } + + @Test + void tryAcquireWithZeroTimeoutFailsImmediately() throws Exception { + var window = new FlowControlWindow(100); + boolean acquired = window.tryAcquire(200, 0); + + assertFalse(acquired); + } + + @Test + void concurrentAcquireAndRelease() throws Exception { + var window = new FlowControlWindow(1000); + int threads = 10; + int iterations = 100; + + Thread[] acquirers = new Thread[threads]; + for (int i = 0; i < threads; i++) { + acquirers[i] = Thread.startVirtualThread(() -> { + try { + for (int j = 0; j < iterations; j++) { + window.tryAcquire(10, 1000); + window.release(10); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + } + + for (Thread t : acquirers) { + t.join(5000); + } + + assertEquals(1000, window.available(), "Window should be back to initial"); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2StreamStateTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2StreamStateTest.java new file mode 100644 index 000000000..6a3ee6159 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2StreamStateTest.java @@ -0,0 +1,131 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class H2StreamStateTest { + + @Test + void initialStateIsCorrect() { + var state = new H2StreamState(); + + assertFalse(state.isResponseHeadersReceived()); + assertFalse(state.isEndStreamSent()); + assertFalse(state.isEndStreamReceived()); + assertEquals(H2StreamState.RS_WAITING, state.getReadState()); + } + + @Test + void setResponseHeadersReceivedTransitionsState() { + var state = new H2StreamState(); + state.setResponseHeadersReceived(200); + + assertTrue(state.isResponseHeadersReceived()); + assertEquals(200, state.getStatusCode()); + assertEquals(H2StreamState.RS_READING, state.getReadState()); + } + + @Test + void markEndStreamSentSetsFlag() { + var state = new H2StreamState(); + state.markEndStreamSent(); + + assertTrue(state.isEndStreamSent()); + } + + @Test + void markEndStreamReceivedSetsReadStateDone() { + var state = new H2StreamState(); + state.setResponseHeadersReceived(200); + state.markEndStreamReceived(); + + assertTrue(state.isEndStreamReceived()); + assertEquals(H2StreamState.RS_DONE, state.getReadState()); + } + + @Test + void setErrorStateSetsReadStateError() { + var state = new H2StreamState(); + state.setErrorState(); + + assertEquals(H2StreamState.RS_ERROR, state.getReadState()); + } + + @Test + void streamStateTransitionsCorrectly() { + var state = new H2StreamState(); + + // Initial: IDLE + assertEquals(H2StreamState.SS_IDLE, state.getStreamState()); + + // After headers encoded without endStream -> OPEN + state.onHeadersEncoded(false); + assertEquals(H2StreamState.SS_OPEN, state.getStreamState()); + + // After headers encoded with endStream -> HALF_CLOSED_LOCAL + var state2 = new H2StreamState(); + state2.onHeadersEncoded(true); + assertEquals(H2StreamState.SS_HALF_CLOSED_LOCAL, state2.getStreamState()); + } + + @Test + void halfClosedLocalToClosedOnEndStreamReceived() { + var state = new H2StreamState(); + state.onHeadersEncoded(true); // IDLE -> HALF_CLOSED_LOCAL + state.setResponseHeadersReceived(200); + + state.markEndStreamReceived(); + + assertEquals(H2StreamState.SS_CLOSED, state.getStreamState()); + } + + @Test + void halfClosedRemoteToClosedOnEndStreamSent() { + var state = new H2StreamState(); + state.onHeadersEncoded(false); // IDLE -> OPEN + state.setResponseHeadersReceived(200); + state.markEndStreamReceived(); // OPEN -> HALF_CLOSED_REMOTE + + assertEquals(H2StreamState.SS_HALF_CLOSED_REMOTE, state.getStreamState()); + + state.markEndStreamSent(); // HALF_CLOSED_REMOTE -> CLOSED + + assertEquals(H2StreamState.SS_CLOSED, state.getStreamState()); + } + + @Test + void setStreamStateClosedWorks() { + var state = new H2StreamState(); + state.setStreamStateClosed(); + + assertEquals(H2StreamState.SS_CLOSED, state.getStreamState()); + } + + @Test + void setReadStateDoneWorks() { + var state = new H2StreamState(); + state.setReadStateDone(); + + assertEquals(H2StreamState.RS_DONE, state.getReadState()); + } + + @Test + void setEndStreamReceivedFlagOnlySetsFlag() { + var state = new H2StreamState(); + state.setResponseHeadersReceived(200); + + state.setEndStreamReceivedFlag(); + + assertTrue(state.isEndStreamReceived()); + assertEquals(H2StreamState.RS_DONE, state.getReadState()); + // Stream state should NOT change (unlike markEndStreamReceived) + } +} diff --git a/io/src/main/java/software/amazon/smithy/java/io/ByteBufferUtils.java b/io/src/main/java/software/amazon/smithy/java/io/ByteBufferUtils.java index 1e237e9be..9b0be82e7 100644 --- a/io/src/main/java/software/amazon/smithy/java/io/ByteBufferUtils.java +++ b/io/src/main/java/software/amazon/smithy/java/io/ByteBufferUtils.java @@ -5,7 +5,9 @@ package software.amazon.smithy.java.io; +import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -75,5 +77,16 @@ public int read(byte[] bytes, int off, int len) { b.get(bytes, off, len); return len; } + + @Override + public long transferTo(OutputStream out) throws IOException { + // Skip buffering used in the default implementation. + int remaining = b.remaining(); + if (remaining > 0 && b.hasArray()) { + out.write(b.array(), b.arrayOffset() + b.position(), remaining); + b.position(b.limit()); + } + return remaining; + } } } From bb0a7a2e5d7363c175317fdcecfc4c9f22b7057a Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 29 Jan 2026 18:25:13 -0600 Subject: [PATCH 38/60] Flush only once for h1 --- .../amazon/smithy/java/http/client/h1/H1Exchange.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java index d665d7d01..16f6cf7b9 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java @@ -102,7 +102,10 @@ public final class H1Exchange implements HttpExchange { UnsyncBufferedOutputStream out = connection.getOutputStream(); writeRequestLine(out); writeHeaders(out, request.headers()); - out.flush(); + // Only flush if no body - otherwise body write will flush + if (request.body() == null || request.body().contentLength() == 0) { + out.flush(); + } } @Override From a66b0f48f3c9ee0a070fd5fc0100b8d565b6bcdc Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Fri, 30 Jan 2026 16:53:19 -0600 Subject: [PATCH 39/60] Send WINDOW_UPDATE on data arrival, add overrides --- .../java/http/client/H1ScalingBenchmark.java | 4 +- .../client/DelegatedClosingInputStream.java | 5 +- .../http/client/h2/H2DataInputStream.java | 30 +++++++++++ .../java/http/client/h2/H2Exchange.java | 53 ++++++++----------- 4 files changed, 56 insertions(+), 36 deletions(-) diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java index 64bde0c8b..46a8eeaa5 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java @@ -60,10 +60,10 @@ @State(Scope.Benchmark) public class H1ScalingBenchmark { - @Param({"100", "500", "1000", "2000"}) + @Param({"100", "500"}) private int concurrency; - @Param({"50", "100", "200", "500"}) + @Param({"50", "100"}) private int maxConnections; private HttpClient smithyClient; diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java index 95d25e2f8..ff0685975 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java @@ -8,6 +8,7 @@ import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -25,8 +26,8 @@ public DelegatedClosingInputStream(InputStream delegate, CloseCallback closeCall } @Override - public int read(byte[] b, int off, int len) throws IOException { - return in.read(b, off, len); + public long transferTo(OutputStream out) throws IOException { + return in.transferTo(out); } @Override diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java index 860da0b27..e9eeaa9b5 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java @@ -138,6 +138,36 @@ public int available() { return currentLength - readPosition; } + @Override + public long skip(long n) throws IOException { + if (closed || eof || n <= 0) { + return 0; + } + + long skipped = 0; + + // Skip from current buffer first + if (currentBuffer != null && readPosition < currentLength) { + int available = currentLength - readPosition; + int toSkip = (int) Math.min(available, n); + readPosition += toSkip; + exchange.onDataConsumed(toSkip); + skipped += toSkip; + n -= toSkip; + } + + // Skip whole chunks without copying + while (n > 0 && pullNextChunk()) { + int toSkip = (int) Math.min(currentLength, n); + readPosition = toSkip; + exchange.onDataConsumed(toSkip); + skipped += toSkip; + n -= toSkip; + } + + return skipped; + } + @Override public void close() { if (closed) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index d916f2fd9..6472ad0d4 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -419,13 +419,26 @@ void signalStreamError(H2Exception error) { */ void enqueueData(byte[] data, int length, boolean endStream, boolean moreDataBuffered) { boolean shouldSignal; + boolean sendWindowUpdate = false; + int windowIncrement = 0; + dataLock.lock(); try { int queueSizeBefore = dataQueue.size(); if (data != null && length > 0) { dataQueue.add(new DataChunk(data, length, endStream)); - // Note: onReadActivity moved to drainChunks (consumer side) for batching + + // Update stream receive window immediately when data arrives. + // This allows the server to keep sending without waiting for consumer to read. + // Buffering is bounded by initialWindowSize - server cannot send more than + // the window allows, so a slow consumer can buffer at most initialWindowSize bytes. + streamRecvWindow -= length; + if (streamRecvWindow < initialWindowSize / H2Constants.WINDOW_UPDATE_THRESHOLD_DIVISOR) { + windowIncrement = initialWindowSize - streamRecvWindow; + streamRecvWindow += windowIncrement; + sendWindowUpdate = true; + } } else if (data != null) { // Empty buffer - return to pool immediately muxer.returnBuffer(data); @@ -455,6 +468,11 @@ void enqueueData(byte[] data, int length, boolean endStream, boolean moreDataBuf dataLock.unlock(); } + // Send WINDOW_UPDATE outside lock to avoid blocking reader thread + if (sendWindowUpdate) { + muxer.queueControlFrame(streamId, H2Muxer.ControlFrameType.WINDOW_UPDATE, windowIncrement, writeTimeoutMs); + } + // Signal outside lock - lock-free wakeup if (shouldSignal) { Thread t = waitingThread; @@ -568,24 +586,13 @@ int drainChunks(DataChunk[] dest, int maxChunks) throws IOException { /** * Called by H2DataInputStream when data is consumed. * - *

      Updates content length tracking and flow control. Sends WINDOW_UPDATE - * when the receive window drops below the threshold. + *

      Updates content length tracking. Note: flow control WINDOW_UPDATE is sent + * in {@link #enqueueData} when data arrives, not here. * * @param bytesConsumed number of bytes consumed */ void onDataConsumed(int bytesConsumed) { receivedContentLength += bytesConsumed; - - // Update flow control if stream is still open - if (state.getReadState() != RS_DONE) { - try { - updateStreamRecvWindow(bytesConsumed); - } catch (IOException e) { - // Flow control update failed - best effort, log and continue. - // The stream will eventually fail on timeout if this is persistent. - LOGGER.debug("Failed to send WINDOW_UPDATE for stream {}: {}", streamId, e.getMessage()); - } - } } /** @@ -1021,24 +1028,6 @@ void sendEndStream() { } } - /** - * Update stream receive window after consuming data. - * - *

      Sends WINDOW_UPDATE when the window drops below the threshold defined by - * {@link H2Constants#WINDOW_UPDATE_THRESHOLD_DIVISOR}. - * - * @throws IOException if the write queue is full - */ - private void updateStreamRecvWindow(int bytesConsumed) throws IOException { - streamRecvWindow -= bytesConsumed; - if (streamRecvWindow < initialWindowSize / H2Constants.WINDOW_UPDATE_THRESHOLD_DIVISOR) { - int increment = initialWindowSize - streamRecvWindow; - // Queue stream-level WINDOW_UPDATE. Writer thread will send it. - muxer.queueControlFrame(streamId, H2Muxer.ControlFrameType.WINDOW_UPDATE, increment, writeTimeoutMs); - streamRecvWindow += increment; - } - } - @Override public HttpHeaders responseTrailerHeaders() { return trailerHeaders; From 82724fffec33b5a9fb23fbac33402740b6f74d0e Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Tue, 3 Feb 2026 15:48:33 -0600 Subject: [PATCH 40/60] Improve benchmarks to use concurrency and add Java HTTP client --- .../java/http/client/H1ScalingBenchmark.java | 60 +++++++++++++++++-- .../java/http/client/H2cScalingBenchmark.java | 18 +++--- 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java index 46a8eeaa5..510bf135e 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java @@ -7,7 +7,11 @@ import io.helidon.webclient.api.HttpClientResponse; import io.helidon.webclient.api.WebClient; +import java.io.InputStream; +import java.io.OutputStream; import java.net.URI; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse.BodyHandlers; import java.time.Duration; import java.util.concurrent.TimeUnit; import org.apache.hc.client5.http.classic.methods.HttpGet; @@ -69,6 +73,7 @@ public class H1ScalingBenchmark { private HttpClient smithyClient; private CloseableHttpClient apacheClient; private WebClient helidonClient; + private java.net.http.HttpClient javaClient; @Setup(Level.Iteration) public void setupIteration() throws Exception { @@ -110,6 +115,11 @@ public void setupIteration() throws Exception { .connectionCacheSize(maxConnections) .build(); + // Java HttpClient (HTTP/1.1) + javaClient = java.net.http.HttpClient.newBuilder() + .version(java.net.http.HttpClient.Version.HTTP_1_1) + .build(); + BenchmarkSupport.resetServer(smithyClient, BenchmarkSupport.H1_URL); } @@ -137,6 +147,10 @@ private void closeClients() throws Exception { helidonClient.closeResource(); helidonClient = null; } + if (javaClient != null) { + javaClient.close(); + javaClient = null; + } } @AuxCounters(AuxCounters.Type.EVENTS) @@ -154,7 +168,7 @@ public void smithy(Counter counter) throws InterruptedException { var uri = URI.create(BenchmarkSupport.H1_URL + "/get"); var request = HttpRequest.builder().uri(uri).method("GET").build(); - BenchmarkSupport.runBenchmark(concurrency, 1000, (HttpRequest req) -> { + BenchmarkSupport.runBenchmark(concurrency, concurrency, (HttpRequest req) -> { smithyClient.send(req).close(); }, request, counter); @@ -166,7 +180,7 @@ public void smithy(Counter counter) throws InterruptedException { public void apache(Counter counter) throws InterruptedException { var target = BenchmarkSupport.H1_URL + "/get"; - BenchmarkSupport.runBenchmark(concurrency, 1000, (String url) -> { + BenchmarkSupport.runBenchmark(concurrency, concurrency, (String url) -> { try (var response = apacheClient.execute(new HttpGet(url))) { EntityUtils.consume(response.getEntity()); } @@ -178,7 +192,7 @@ public void apache(Counter counter) throws InterruptedException { @Benchmark @Threads(1) public void helidon(Counter counter) throws InterruptedException { - BenchmarkSupport.runBenchmark(concurrency, 1000, (WebClient client) -> { + BenchmarkSupport.runBenchmark(concurrency, concurrency, (WebClient client) -> { try (HttpClientResponse response = client.get("/get").request()) { response.entity().consume(); } @@ -187,6 +201,24 @@ public void helidon(Counter counter) throws InterruptedException { counter.logErrors("Helidon H1"); } + @Benchmark + @Threads(1) + public void javaHttpClient(Counter counter) throws InterruptedException { + var request = java.net.http.HttpRequest.newBuilder() + .uri(URI.create(BenchmarkSupport.H1_URL + "/get")) + .GET() + .build(); + + BenchmarkSupport.runBenchmark(concurrency, concurrency, (java.net.http.HttpRequest req) -> { + var response = javaClient.send(req, BodyHandlers.ofInputStream()); + try (InputStream body = response.body()) { + body.transferTo(OutputStream.nullOutputStream()); + } + }, request, counter); + + counter.logErrors("Java HttpClient H1"); + } + @Benchmark @Threads(1) public void smithyPost(Counter counter) throws InterruptedException { @@ -197,7 +229,7 @@ public void smithyPost(Counter counter) throws InterruptedException { .body(DataStream.ofBytes(BenchmarkSupport.POST_PAYLOAD)) .build(); - BenchmarkSupport.runBenchmark(concurrency, 1000, (HttpRequest req) -> { + BenchmarkSupport.runBenchmark(concurrency, concurrency, (HttpRequest req) -> { smithyClient.send(req).close(); }, request, counter); @@ -209,7 +241,7 @@ public void smithyPost(Counter counter) throws InterruptedException { public void apachePost(Counter counter) throws InterruptedException { var target = BenchmarkSupport.H1_URL + "/post"; - BenchmarkSupport.runBenchmark(concurrency, 1000, (String url) -> { + BenchmarkSupport.runBenchmark(concurrency, concurrency, (String url) -> { var post = new HttpPost(url); post.setEntity(new ByteArrayEntity(BenchmarkSupport.POST_PAYLOAD, ContentType.APPLICATION_OCTET_STREAM)); try (var response = apacheClient.execute(post)) { @@ -219,4 +251,22 @@ public void apachePost(Counter counter) throws InterruptedException { counter.logErrors("Apache H1 POST"); } + + @Benchmark + @Threads(1) + public void javaHttpClientPost(Counter counter) throws InterruptedException { + var request = java.net.http.HttpRequest.newBuilder() + .uri(URI.create(BenchmarkSupport.H1_URL + "/post")) + .POST(BodyPublishers.ofByteArray(BenchmarkSupport.POST_PAYLOAD)) + .build(); + + BenchmarkSupport.runBenchmark(concurrency, concurrency, (java.net.http.HttpRequest req) -> { + var response = javaClient.send(req, BodyHandlers.ofInputStream()); + try (InputStream body = response.body()) { + body.transferTo(OutputStream.nullOutputStream()); + } + }, request, counter); + + counter.logErrors("Java HttpClient H1 POST"); + } } diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java index d7314ff98..3268ab30b 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java @@ -226,7 +226,7 @@ public void smithy(Counter counter) throws InterruptedException { var uri = URI.create(BenchmarkSupport.H2C_URL + "/get"); var request = HttpRequest.builder().uri(uri).method("GET").build(); - BenchmarkSupport.runBenchmark(concurrency, 1000, (HttpRequest req) -> { + BenchmarkSupport.runBenchmark(concurrency, concurrency, (HttpRequest req) -> { try (var res = smithyClient.send(req)) { res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); } @@ -238,7 +238,7 @@ public void smithy(Counter counter) throws InterruptedException { @Benchmark @Threads(1) public void helidon(Counter counter) throws InterruptedException { - BenchmarkSupport.runBenchmark(concurrency, 1000, (Http2Client client) -> { + BenchmarkSupport.runBenchmark(concurrency, concurrency, (Http2Client client) -> { try (HttpClientResponse response = client.get("/get").request()) { response.entity().consume(); } @@ -257,7 +257,7 @@ public void smithyPost(Counter counter) throws InterruptedException { .body(DataStream.ofBytes(BenchmarkSupport.POST_PAYLOAD)) .build(); - BenchmarkSupport.runBenchmark(concurrency, 1000, (HttpRequest req) -> { + BenchmarkSupport.runBenchmark(concurrency, concurrency, (HttpRequest req) -> { try (var res = smithyClient.send(req)) { res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); } @@ -276,7 +276,7 @@ public void smithyPutMb(Counter counter) throws InterruptedException { .body(DataStream.ofBytes(BenchmarkSupport.MB_PAYLOAD)) .build(); - BenchmarkSupport.runBenchmark(concurrency, 1000, (HttpRequest req) -> { + BenchmarkSupport.runBenchmark(concurrency, concurrency, (HttpRequest req) -> { try (var res = smithyClient.send(req)) { res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); } @@ -291,7 +291,7 @@ public void smithyGetMb(Counter counter) throws InterruptedException { var uri = URI.create(BenchmarkSupport.H2C_URL + "/getmb"); var request = HttpRequest.builder().uri(uri).method("GET").build(); - BenchmarkSupport.runBenchmark(concurrency, 1000, (HttpRequest req) -> { + BenchmarkSupport.runBenchmark(concurrency, concurrency, (HttpRequest req) -> { try (var res = smithyClient.send(req)) { res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); } @@ -312,7 +312,7 @@ public void netty(Counter counter) throws Exception { var connectionIndex = new AtomicInteger(0); - BenchmarkSupport.runBenchmark(concurrency, 1000, (DefaultHttp2Headers h) -> { + BenchmarkSupport.runBenchmark(concurrency, concurrency, (DefaultHttp2Headers h) -> { var latch = new CountDownLatch(1); var error = new AtomicReference(); @@ -370,7 +370,7 @@ public void nettyGetMb(Counter counter) throws Exception { var connectionIndex = new AtomicInteger(0); - BenchmarkSupport.runBenchmark(concurrency, 1000, (DefaultHttp2Headers h) -> { + BenchmarkSupport.runBenchmark(concurrency, concurrency, (DefaultHttp2Headers h) -> { var latch = new CountDownLatch(1); var error = new AtomicReference(); @@ -437,7 +437,7 @@ public void nettyPost(Counter counter) throws Exception { var connectionIndex = new AtomicInteger(0); - BenchmarkSupport.runBenchmark(concurrency, 1000, (DefaultHttp2Headers h) -> { + BenchmarkSupport.runBenchmark(concurrency, concurrency, (DefaultHttp2Headers h) -> { var latch = new CountDownLatch(1); var error = new AtomicReference(); @@ -501,7 +501,7 @@ public void nettyPutMb(Counter counter) throws Exception { var connectionIndex = new AtomicInteger(0); - BenchmarkSupport.runBenchmark(concurrency, 1000, (DefaultHttp2Headers h) -> { + BenchmarkSupport.runBenchmark(concurrency, concurrency, (DefaultHttp2Headers h) -> { var latch = new CountDownLatch(1); var error = new AtomicReference(); From c37dff99ee16f7c6281680dd16f7e35a2b931eb9 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Tue, 3 Feb 2026 16:28:45 -0600 Subject: [PATCH 41/60] Add h2 benchmark, fix flowcontrol issue --- .../java/http/client/H2ScalingBenchmark.java | 264 ++++++++++++++++++ .../http/client/h2/FlowControlWindow.java | 31 +- .../smithy/java/http/client/h2/H2Muxer.java | 72 ++++- .../http/client/h2/FlowControlWindowTest.java | 68 ++--- 4 files changed, 365 insertions(+), 70 deletions(-) create mode 100644 http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2ScalingBenchmark.java diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2ScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2ScalingBenchmark.java new file mode 100644 index 000000000..57d387885 --- /dev/null +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2ScalingBenchmark.java @@ -0,0 +1,264 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse.BodyHandlers; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.AuxCounters; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.io.datastream.DataStream; + +/** + * HTTP/2 over TLS (h2) benchmark comparing Smithy and Java HttpClient. + * + *

      Run with: ./gradlew :http:http-client:jmh -Pjmh.includes="H2ScalingBenchmark" + */ +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +@Warmup(iterations = 2, time = 3) +@Measurement(iterations = 3, time = 5) +@Fork(value = 1, jvmArgs = {"-Xms2g", "-Xmx2g"}) +@State(Scope.Benchmark) +public class H2ScalingBenchmark { + + @Param({"1000"}) + private int concurrency; + + @Param({"3"}) + private int connections; + + @Param({"4096"}) + private int streamsPerConnection; + + private HttpClient smithyClient; + private java.net.http.HttpClient javaClient; + + @Setup(Level.Iteration) + public void setupIteration() throws Exception { + closeClients(); + + System.out.println("H2 setup: concurrency=" + concurrency + + ", connections=" + connections + + ", streams=" + streamsPerConnection); + + var sslContext = BenchmarkSupport.trustAllSsl(); + + // Smithy H2 client + smithyClient = HttpClient.builder() + .connectionPool(HttpConnectionPool.builder() + .maxConnectionsPerRoute(connections) + .maxTotalConnections(connections) + .h2StreamsPerConnection(streamsPerConnection) + .h2InitialWindowSize(1024 * 1024) + .maxIdleTime(Duration.ofMinutes(2)) + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_2) + .sslContext(sslContext) + .dnsResolver(BenchmarkSupport.staticDns()) + .build()) + .build(); + + // Java HttpClient (HTTP/2 over TLS) + javaClient = java.net.http.HttpClient.newBuilder() + .version(java.net.http.HttpClient.Version.HTTP_2) + .sslContext(sslContext) + .build(); + + BenchmarkSupport.resetServer(smithyClient, BenchmarkSupport.H2_URL); + } + + @TearDown(Level.Iteration) + public void teardownIteration() throws Exception { + String stats = BenchmarkSupport.getServerStats(smithyClient, BenchmarkSupport.H2_URL); + System.out.println("H2 stats [c=" + concurrency + ", conn=" + connections + + ", streams=" + streamsPerConnection + "]: " + stats); + } + + @TearDown + public void teardown() throws Exception { + closeClients(); + } + + private void closeClients() throws Exception { + if (smithyClient != null) { + smithyClient.close(); + smithyClient = null; + } + if (javaClient != null) { + javaClient.close(); + javaClient = null; + } + } + + @AuxCounters(AuxCounters.Type.EVENTS) + @State(Scope.Thread) + public static class Counter extends BenchmarkSupport.RequestCounter { + @Setup(Level.Iteration) + public void reset() { + super.reset(); + } + } + + @Benchmark + @Threads(1) + public void smithy(Counter counter) throws InterruptedException { + var uri = URI.create(BenchmarkSupport.H2_URL + "/get"); + var request = HttpRequest.builder().uri(uri).method("GET").build(); + + BenchmarkSupport.runBenchmark(concurrency, concurrency, (HttpRequest req) -> { + try (var res = smithyClient.send(req)) { + res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); + } + }, request, counter); + + counter.logErrors("Smithy H2"); + } + + @Benchmark + @Threads(1) + public void javaHttpClient(Counter counter) throws InterruptedException { + var request = java.net.http.HttpRequest.newBuilder() + .uri(URI.create(BenchmarkSupport.H2_URL + "/get")) + .GET() + .build(); + + BenchmarkSupport.runBenchmark(concurrency, concurrency, (java.net.http.HttpRequest req) -> { + var response = javaClient.send(req, BodyHandlers.ofInputStream()); + try (InputStream body = response.body()) { + body.transferTo(OutputStream.nullOutputStream()); + } + }, request, counter); + + counter.logErrors("Java HttpClient H2"); + } + + @Benchmark + @Threads(1) + public void smithyPost(Counter counter) throws InterruptedException { + var uri = URI.create(BenchmarkSupport.H2_URL + "/post"); + var request = HttpRequest.builder() + .uri(uri) + .method("POST") + .body(DataStream.ofBytes(BenchmarkSupport.POST_PAYLOAD)) + .build(); + + BenchmarkSupport.runBenchmark(concurrency, concurrency, (HttpRequest req) -> { + try (var res = smithyClient.send(req)) { + res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); + } + }, request, counter); + + counter.logErrors("Smithy H2 POST"); + } + + @Benchmark + @Threads(1) + public void javaHttpClientPost(Counter counter) throws InterruptedException { + var request = java.net.http.HttpRequest.newBuilder() + .uri(URI.create(BenchmarkSupport.H2_URL + "/post")) + .POST(BodyPublishers.ofByteArray(BenchmarkSupport.POST_PAYLOAD)) + .build(); + + BenchmarkSupport.runBenchmark(concurrency, concurrency, (java.net.http.HttpRequest req) -> { + var response = javaClient.send(req, BodyHandlers.ofInputStream()); + try (InputStream body = response.body()) { + body.transferTo(OutputStream.nullOutputStream()); + } + }, request, counter); + + counter.logErrors("Java HttpClient H2 POST"); + } + + @Benchmark + @Threads(1) + public void smithyPutMb(Counter counter) throws InterruptedException { + var uri = URI.create(BenchmarkSupport.H2_URL + "/putmb"); + var request = HttpRequest.builder() + .uri(uri) + .method("PUT") + .body(DataStream.ofBytes(BenchmarkSupport.MB_PAYLOAD)) + .build(); + + BenchmarkSupport.runBenchmark(concurrency, concurrency, (HttpRequest req) -> { + try (var res = smithyClient.send(req)) { + res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); + } + }, request, counter); + + counter.logErrors("Smithy H2 PUT 1MB"); + } + + @Benchmark + @Threads(1) + public void javaHttpClientPutMb(Counter counter) throws InterruptedException { + var request = java.net.http.HttpRequest.newBuilder() + .uri(URI.create(BenchmarkSupport.H2_URL + "/putmb")) + .PUT(BodyPublishers.ofByteArray(BenchmarkSupport.MB_PAYLOAD)) + .build(); + + BenchmarkSupport.runBenchmark(concurrency, concurrency, (java.net.http.HttpRequest req) -> { + var response = javaClient.send(req, BodyHandlers.ofInputStream()); + try (InputStream body = response.body()) { + body.transferTo(OutputStream.nullOutputStream()); + } + }, request, counter); + + counter.logErrors("Java HttpClient H2 PUT 1MB"); + } + + @Benchmark + @Threads(1) + public void smithyGetMb(Counter counter) throws InterruptedException { + var uri = URI.create(BenchmarkSupport.H2_URL + "/getmb"); + var request = HttpRequest.builder().uri(uri).method("GET").build(); + + BenchmarkSupport.runBenchmark(concurrency, concurrency, (HttpRequest req) -> { + try (var res = smithyClient.send(req)) { + res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); + } + }, request, counter); + + counter.logErrors("Smithy H2 GET 1MB"); + } + + @Benchmark + @Threads(1) + public void javaHttpClientGetMb(Counter counter) throws InterruptedException { + var request = java.net.http.HttpRequest.newBuilder() + .uri(URI.create(BenchmarkSupport.H2_URL + "/getmb")) + .GET() + .build(); + + BenchmarkSupport.runBenchmark(concurrency, concurrency, (java.net.http.HttpRequest req) -> { + var response = javaClient.send(req, BodyHandlers.ofInputStream()); + try (InputStream body = response.body()) { + body.transferTo(OutputStream.nullOutputStream()); + } + }, request, counter); + + counter.logErrors("Java HttpClient H2 GET 1MB"); + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java index 42846117a..39db3fa00 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java @@ -30,33 +30,20 @@ final class FlowControlWindow { } /** - * Try to acquire bytes from the window with a timeout. + * Try to acquire up to the requested bytes from the window without blocking. * - * @param bytes number of bytes to acquire - * @param timeoutMs maximum time to wait in milliseconds - * @return true if bytes acquired, false if timeout expired - * @throws InterruptedException if interrupted while waiting + * @param maxBytes maximum number of bytes to acquire + * @return number of bytes acquired (0 if window is empty) */ - boolean tryAcquire(int bytes, long timeoutMs) throws InterruptedException { + int tryAcquireNonBlocking(int maxBytes) { lock.lock(); try { - // Fast path: no waiting needed - if (window >= bytes) { - window -= bytes; - return true; - } - - // Slow path: wait with timeout - long remainingNs = TimeUnit.MILLISECONDS.toNanos(timeoutMs); - while (window < bytes) { - if (remainingNs <= 0) { - return false; - } - remainingNs = available.awaitNanos(remainingNs); + if (window > 0) { + int acquired = (int) Math.min(window, maxBytes); + window -= acquired; + return acquired; } - - window -= bytes; - return true; + return 0; } finally { lock.unlock(); } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java index 9427b3d57..84f1c4b33 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java @@ -88,6 +88,23 @@ enum ControlFrameType { // === CONNECTION FLOW CONTROL === private final FlowControlWindow connectionSendWindow; + private final ConcurrentLinkedQueue sendWindowWaiters = new ConcurrentLinkedQueue<>(); + + /** + * Waiter for connection send window. Used for fair FIFO queuing. + */ + private static final class SendWindowWaiter { + final Thread thread; + final int maxBytes; + volatile int acquired; + volatile boolean done; + volatile boolean cancelled; + + SendWindowWaiter(Thread thread, int maxBytes) { + this.thread = thread; + this.maxBytes = maxBytes; + } + } // === STATE === private volatile boolean accepting = true; @@ -293,13 +310,40 @@ void onGoaway(int lastStreamId, int errorCode) { /** * Acquire up to the requested bytes from the connection flow control window. + * Uses FIFO queuing to prevent thundering herd and starvation. * * @param maxBytes maximum bytes to acquire * @param timeoutMs timeout if window is empty * @return bytes acquired (0 if timeout) */ int acquireConnectionWindowUpTo(int maxBytes, long timeoutMs) throws SocketTimeoutException, InterruptedException { - return connectionSendWindow.tryAcquireUpTo(maxBytes, timeoutMs); + // Fast path: no waiters and window available + if (sendWindowWaiters.isEmpty()) { + int acquired = connectionSendWindow.tryAcquireNonBlocking(maxBytes); + if (acquired > 0) { + return acquired; + } + } + + // Slow path: queue and wait for fair access + var waiter = new SendWindowWaiter(Thread.currentThread(), maxBytes); + sendWindowWaiters.add(waiter); + + try { + int deadlineTick = deadlineTick(timeoutMs); + while (!waiter.done) { + if (deadlineTick > 0 && timeoutTick >= deadlineTick) { + return 0; // Timeout + } + LockSupport.parkNanos(TIMEOUT_POLL_INTERVAL_MS * 1_000_000L); + if (Thread.interrupted()) { + throw new InterruptedException(); + } + } + return waiter.acquired; + } finally { + waiter.cancelled = true; // wakeWaiters() will skip and remove + } } void releaseConnectionWindow(int bytes) { @@ -307,6 +351,32 @@ void releaseConnectionWindow(int bytes) { if ((long) currentWindow + bytes <= Integer.MAX_VALUE) { connectionSendWindow.release(bytes); } + + wakeWaiters(); + } + + /** + * Wake queued waiters in FIFO order until window is exhausted. + */ + private void wakeWaiters() { + SendWindowWaiter waiter; + while ((waiter = sendWindowWaiters.peek()) != null) { + // Skip cancelled waiters + if (waiter.cancelled) { + sendWindowWaiters.poll(); + continue; + } + int acquired = connectionSendWindow.tryAcquireNonBlocking(waiter.maxBytes); + if (acquired > 0) { + waiter.acquired = acquired; + waiter.done = true; + sendWindowWaiters.poll(); + LockSupport.unpark(waiter.thread); + } else { + // No more window available + break; + } + } } // ==================== WRITE QUEUE ==================== diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/FlowControlWindowTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/FlowControlWindowTest.java index 0d21b1e01..9bccd37b7 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/FlowControlWindowTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/FlowControlWindowTest.java @@ -6,8 +6,6 @@ package software.amazon.smithy.java.http.client.h2; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; @@ -16,26 +14,33 @@ class FlowControlWindowTest { @Test void initialWindowIsAvailable() { var window = new FlowControlWindow(65535); - assertEquals(65535, window.available()); } @Test - void tryAcquireReducesWindow() throws Exception { + void tryAcquireNonBlockingReducesWindow() { var window = new FlowControlWindow(1000); - boolean acquired = window.tryAcquire(400, 100); + int acquired = window.tryAcquireNonBlocking(400); - assertTrue(acquired); + assertEquals(400, acquired); assertEquals(600, window.available()); } @Test - void tryAcquireBlocksWhenInsufficient() throws Exception { + void tryAcquireNonBlockingReturnsZeroWhenEmpty() { + var window = new FlowControlWindow(0); + int acquired = window.tryAcquireNonBlocking(200); + + assertEquals(0, acquired); + } + + @Test + void tryAcquireNonBlockingAcquiresPartial() { var window = new FlowControlWindow(100); - // Try to acquire more than available with short timeout - boolean acquired = window.tryAcquire(200, 50); + int acquired = window.tryAcquireNonBlocking(200); - assertFalse(acquired, "Should timeout when insufficient"); + assertEquals(100, acquired); + assertEquals(0, window.available()); } @Test @@ -46,25 +51,6 @@ void releaseIncreasesWindow() { assertEquals(1500, window.available()); } - @Test - void releaseWakesWaitingThread() throws Exception { - var window = new FlowControlWindow(100); - - Thread acquirer = Thread.startVirtualThread(() -> { - try { - window.tryAcquire(200, 5000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - }); - - Thread.sleep(50); // Let acquirer start waiting - window.release(200); // Release enough - acquirer.join(1000); - - assertEquals(100, window.available()); // 100 + 200 - 200 = 100 - } - @Test void adjustIncreasesWindow() { var window = new FlowControlWindow(1000); @@ -89,35 +75,23 @@ void adjustCanMakeWindowNegative() { assertEquals(-100, window.available()); } - @Test - void tryAcquireWithZeroTimeoutFailsImmediately() throws Exception { - var window = new FlowControlWindow(100); - boolean acquired = window.tryAcquire(200, 0); - - assertFalse(acquired); - } - @Test void concurrentAcquireAndRelease() throws Exception { var window = new FlowControlWindow(1000); int threads = 10; int iterations = 100; - Thread[] acquirers = new Thread[threads]; + Thread[] workers = new Thread[threads]; for (int i = 0; i < threads; i++) { - acquirers[i] = Thread.startVirtualThread(() -> { - try { - for (int j = 0; j < iterations; j++) { - window.tryAcquire(10, 1000); - window.release(10); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); + workers[i] = Thread.startVirtualThread(() -> { + for (int j = 0; j < iterations; j++) { + window.tryAcquireNonBlocking(10); + window.release(10); } }); } - for (Thread t : acquirers) { + for (Thread t : workers) { t.join(5000); } From e363651e3381652ff4c48ff95636d841d2994dc7 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Tue, 3 Feb 2026 18:22:22 -0600 Subject: [PATCH 42/60] Use Level.Trial for benchmarks to reuse connections --- .../smithy/java/http/client/H1ScalingBenchmark.java | 12 ++++-------- .../smithy/java/http/client/H2ScalingBenchmark.java | 12 ++++-------- .../smithy/java/http/client/H2cScalingBenchmark.java | 12 ++++-------- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java index 510bf135e..ca0d12fc4 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java @@ -75,7 +75,7 @@ public class H1ScalingBenchmark { private WebClient helidonClient; private java.net.http.HttpClient javaClient; - @Setup(Level.Iteration) + @Setup(Level.Trial) public void setupIteration() throws Exception { closeClients(); @@ -123,14 +123,10 @@ public void setupIteration() throws Exception { BenchmarkSupport.resetServer(smithyClient, BenchmarkSupport.H1_URL); } - @TearDown(Level.Iteration) - public void teardownIteration() throws Exception { + @TearDown(Level.Trial) + public void teardown() throws Exception { String stats = BenchmarkSupport.getServerStats(smithyClient, BenchmarkSupport.H1_URL); System.out.println("H1 stats [c=" + concurrency + ", conn=" + maxConnections + "]: " + stats); - } - - @TearDown - public void teardown() throws Exception { closeClients(); } @@ -156,7 +152,7 @@ private void closeClients() throws Exception { @AuxCounters(AuxCounters.Type.EVENTS) @State(Scope.Thread) public static class Counter extends BenchmarkSupport.RequestCounter { - @Setup(Level.Iteration) + @Setup(Level.Trial) public void reset() { super.reset(); } diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2ScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2ScalingBenchmark.java index 57d387885..06e7d4f83 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2ScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2ScalingBenchmark.java @@ -57,7 +57,7 @@ public class H2ScalingBenchmark { private HttpClient smithyClient; private java.net.http.HttpClient javaClient; - @Setup(Level.Iteration) + @Setup(Level.Trial) public void setupIteration() throws Exception { closeClients(); @@ -90,15 +90,11 @@ public void setupIteration() throws Exception { BenchmarkSupport.resetServer(smithyClient, BenchmarkSupport.H2_URL); } - @TearDown(Level.Iteration) - public void teardownIteration() throws Exception { + @TearDown(Level.Trial) + public void teardown() throws Exception { String stats = BenchmarkSupport.getServerStats(smithyClient, BenchmarkSupport.H2_URL); System.out.println("H2 stats [c=" + concurrency + ", conn=" + connections + ", streams=" + streamsPerConnection + "]: " + stats); - } - - @TearDown - public void teardown() throws Exception { closeClients(); } @@ -116,7 +112,7 @@ private void closeClients() throws Exception { @AuxCounters(AuxCounters.Type.EVENTS) @State(Scope.Thread) public static class Counter extends BenchmarkSupport.RequestCounter { - @Setup(Level.Iteration) + @Setup(Level.Trial) public void reset() { super.reset(); } diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java index 3268ab30b..d207657f0 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java @@ -77,7 +77,7 @@ @OutputTimeUnit(TimeUnit.SECONDS) @Warmup(iterations = 2, time = 3) @Measurement(iterations = 3, time = 5) -@Fork(value = 1, jvmArgs = {"-Xms2g", "-Xmx2g"}) +@Fork(value = 1, jvmArgs = {"-Xms2g", "-Xmx2g", "-Xlog:gc*:stdout:time,level,tags"}) @State(Scope.Benchmark) public class H2cScalingBenchmark { @@ -101,7 +101,7 @@ public class H2cScalingBenchmark { private List nettyChannels; private List nettyStreamBootstraps; - @Setup(Level.Iteration) + @Setup(Level.Trial) public void setupIteration() throws Exception { closeClients(); @@ -166,15 +166,11 @@ protected void channelRead0( BenchmarkSupport.resetServer(smithyClient, BenchmarkSupport.H2C_URL); } - @TearDown(Level.Iteration) - public void teardownIteration() throws Exception { + @TearDown(Level.Trial) + public void teardown() throws Exception { String stats = BenchmarkSupport.getServerStats(smithyClient, BenchmarkSupport.H2C_URL); System.out.println("H2c stats [c=" + concurrency + ", conn=" + connections + ", streams=" + streamsPerConnection + "]: " + stats); - } - - @TearDown - public void teardown() throws Exception { closeClients(); } From fda39333d780f22e81c4922c1cf3c343038999e6 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Tue, 3 Feb 2026 18:35:40 -0600 Subject: [PATCH 43/60] Improve HTTP/2 flow control, reduce pinning - Wait for server's initial WINDOW_UPDATE before starting writes to avoid exhausting the 65KB default connection window on large uploads - Migrate H2ConnectionManager from synchronized to ReentrantLock - Add configurable h2BufferSize (default 256KB) for stream buffering - Use short polling in FlowControlWindow to avoid timed-wait contention - Fix buffer leak in H2DataInputStream.transferTo() - Simplify H2Exchange data signaling (signal endStream or buffer empty) --- .../java/http/client/H1ScalingBenchmark.java | 2 +- .../java/http/client/H2ScalingBenchmark.java | 6 ++- .../java/http/client/H2cScalingBenchmark.java | 23 ++++++---- .../connection/H2ConnectionManager.java | 45 ++++++++++++------- .../connection/HttpConnectionFactory.java | 5 ++- .../client/connection/HttpConnectionPool.java | 3 +- .../connection/HttpConnectionPoolBuilder.java | 23 ++++++++++ .../client/connection/HttpSocketFactory.java | 1 - .../http/client/h2/FlowControlWindow.java | 20 ++++++--- .../java/http/client/h2/H2Connection.java | 34 ++++++++++++-- .../http/client/h2/H2DataInputStream.java | 6 +++ .../java/http/client/h2/H2Exchange.java | 31 +++---------- .../java/http/client/h2/H2FrameCodec.java | 4 -- .../smithy/java/http/client/h2/H2Muxer.java | 33 +++++++++----- 14 files changed, 156 insertions(+), 80 deletions(-) diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java index ca0d12fc4..d7b38a170 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H1ScalingBenchmark.java @@ -64,7 +64,7 @@ @State(Scope.Benchmark) public class H1ScalingBenchmark { - @Param({"100", "500"}) + @Param({"1", "10", "100"}) private int concurrency; @Param({"50", "100"}) diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2ScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2ScalingBenchmark.java index 06e7d4f83..d3fc192da 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2ScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2ScalingBenchmark.java @@ -45,7 +45,11 @@ @State(Scope.Benchmark) public class H2ScalingBenchmark { - @Param({"1000"}) + @Param({ + "1", + "10" + //, "100", "1000" + }) private int concurrency; @Param({"3"}) diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java index d207657f0..ce1d941c8 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java @@ -54,6 +54,8 @@ import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.client.connection.ConnectionPoolListener; +import software.amazon.smithy.java.http.client.connection.HttpConnection; import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; import software.amazon.smithy.java.io.datastream.DataStream; @@ -81,16 +83,13 @@ @State(Scope.Benchmark) public class H2cScalingBenchmark { - @Param({"5000"}) + @Param({"10"}) private int concurrency; - @Param({"3", - "5" - //, "10", "20", "50" - }) + @Param({"1", "3", "5", "10"}) private int connections; - @Param({"4096"}) + @Param({"100"}) private int streamsPerConnection; private HttpClient smithyClient; @@ -100,6 +99,7 @@ public class H2cScalingBenchmark { private EventLoopGroup nettyGroup; private List nettyChannels; private List nettyStreamBootstraps; + private AtomicInteger smithyConnectionCount; @Setup(Level.Trial) public void setupIteration() throws Exception { @@ -109,6 +109,8 @@ public void setupIteration() throws Exception { + ", connections=" + connections + ", streams=" + streamsPerConnection); + smithyConnectionCount = new AtomicInteger(0); + // Smithy H2c client smithyClient = HttpClient.builder() .connectionPool(HttpConnectionPool.builder() @@ -119,6 +121,13 @@ public void setupIteration() throws Exception { .maxIdleTime(Duration.ofMinutes(2)) .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) .dnsResolver(BenchmarkSupport.staticDns()) + .addListener(new ConnectionPoolListener() { + @Override + public void onConnected(HttpConnection conn) { + int count = smithyConnectionCount.incrementAndGet(); + System.out.println(" [Smithy] New connection #" + count + ": " + conn); + } + }) .build()) .build(); @@ -293,7 +302,6 @@ public void smithyGetMb(Counter counter) throws InterruptedException { } }, request, counter); - System.out.println("Smithy H2c GET 1MB: " + counter.requests + " requests, " + counter.errors + " errors"); counter.logErrors("Smithy H2c GET 1MB"); } @@ -417,7 +425,6 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { } }, headers, counter); - System.out.println("Netty H2c GET 1MB: " + counter.requests + " requests, " + counter.errors + " errors"); counter.logErrors("Netty H2c GET 1MB"); } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java index ac4345056..172ecd6b2 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java @@ -45,11 +45,15 @@ private static final class RouteState { /** Connections for this route. Volatile for lock-free reads. */ volatile H2Connection[] conns = new H2Connection[0]; - /** Connections currently being created (prevents over-creation). Guarded by sync on this. */ + /** Connections currently being created (prevents over-creation). Guarded by lock. */ int pendingCreations = 0; /** Round-robin index for connection selection. Atomic for concurrent access. */ final AtomicInteger nextIndex = new AtomicInteger(0); + + /** Lock for state modifications. ReentrantLock avoids VT pinning unlike synchronized. */ + final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(); + final java.util.concurrent.locks.Condition available = lock.newCondition(); } private static final H2Connection[] EMPTY = new H2Connection[0]; @@ -57,8 +61,8 @@ private static final class RouteState { // Soft limit as a fraction of streamsPerConnection. When all connections exceed this threshold, // we try to create a new connection (if under max). // This prevents overloading a single TCP connection even when the server allows many streams. - private static final int SOFT_LIMIT_DIVISOR = 8; - private static final int SOFT_LIMIT_FLOOR = 50; + private static final int SOFT_LIMIT_DIVISOR = 100; // Lowered for testing + private static final int SOFT_LIMIT_FLOOR = 1; // Lowered for testing private final ConcurrentHashMap routes = new ConcurrentHashMap<>(); private final int streamsPerConnection; // Hard limit from server @@ -104,7 +108,8 @@ H2Connection acquire(Route route, int maxConnectionsForRoute) throws IOException RouteState state = stateFor(route); long deadline = System.currentTimeMillis() + acquireTimeoutMs; - synchronized (state) { + state.lock.lock(); + try { while (true) { H2Connection[] snapshot = state.conns; int totalConns = snapshot.length + state.pendingCreations; @@ -137,18 +142,18 @@ H2Connection acquire(Route route, int maxConnectionsForRoute) throws IOException } try { - state.wait(remaining); + state.available.await(remaining, java.util.concurrent.TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException("Interrupted while waiting for connection", e); } } + } finally { + state.lock.unlock(); } return createNewH2Connection(route, state); } - - @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") private H2Connection createNewH2Connection(Route route, RouteState state) throws IOException { // Create new connection OUTSIDE the lock to avoid deadlock. H2Connection newConn = null; @@ -159,7 +164,8 @@ private H2Connection createNewH2Connection(Route route, RouteState state) throws createException = e; } finally { // Register under lock (or decrement pending on failure) - synchronized (state) { + state.lock.lock(); + try { state.pendingCreations--; if (newConn != null) { H2Connection[] cur = state.conns; @@ -168,7 +174,9 @@ private H2Connection createNewH2Connection(Route route, RouteState state) throws next[cur.length] = newConn; state.conns = next; } - state.notifyAll(); // Wake waiters + state.available.signalAll(); // Wake waiters + } finally { + state.lock.unlock(); } } @@ -245,7 +253,8 @@ void unregister(Route route, H2Connection conn) { if (state == null) { return; } - synchronized (state) { + state.lock.lock(); + try { H2Connection[] cur = state.conns; int n = cur.length; int idx = -1; @@ -261,13 +270,14 @@ void unregister(Route route, H2Connection conn) { } else if (n == 1) { state.conns = EMPTY; } else { - // Compact array: copy elements before and after removed connection H2Connection[] next = new H2Connection[n - 1]; System.arraycopy(cur, 0, next, 0, idx); System.arraycopy(cur, idx + 1, next, idx, n - idx - 1); state.conns = next; } - state.notifyAll(); // Wake threads waiting for capacity + state.available.signalAll(); + } finally { + state.lock.unlock(); } } @@ -292,7 +302,8 @@ void cleanupDead(Route route, BiConsumer onRemove) { } // Slow path: actually clean up under lock - synchronized (state) { + state.lock.lock(); + try { cur = state.conns; // Re-read under lock int n = cur.length; H2Connection[] tmp = new H2Connection[n]; @@ -315,6 +326,8 @@ void cleanupDead(Route route, BiConsumer onRemove) { System.arraycopy(tmp, 0, next, 0, w); state.conns = next; } + } finally { + state.lock.unlock(); } } @@ -330,7 +343,6 @@ void cleanupAllDead(BiConsumer onRemove) { * @param maxIdleTimeNanos maximum idle time in nanoseconds * @param onRemove callback for removed connections */ - @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") void cleanupIdle(long maxIdleTimeNanos, BiConsumer onRemove) { for (RouteState state : routes.values()) { H2Connection[] cur = state.conns; @@ -348,7 +360,8 @@ void cleanupIdle(long maxIdleTimeNanos, BiConsumer on } // Slow path: clean up under lock - synchronized (state) { + state.lock.lock(); + try { cur = state.conns; // Re-read under lock int n = cur.length; H2Connection[] tmp = new H2Connection[n]; @@ -368,6 +381,8 @@ void cleanupIdle(long maxIdleTimeNanos, BiConsumer on System.arraycopy(tmp, 0, next, 0, w); state.conns = next; } + } finally { + state.lock.unlock(); } } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java index d890c3faa..af385af39 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java @@ -44,7 +44,8 @@ record HttpConnectionFactory( DnsResolver dnsResolver, HttpSocketFactory socketFactory, int h2InitialWindowSize, - int h2MaxFrameSize) { + int h2MaxFrameSize, + int h2BufferSize) { /** * Create a new connection to the given route. * @@ -155,7 +156,7 @@ private HttpConnection createProtocolConnection(Socket socket, Route route) thro try { if ("h2".equals(protocol) || "h2c".equals(protocol)) { - return new H2Connection(socket, route, readTimeout, writeTimeout, h2InitialWindowSize, h2MaxFrameSize); + return new H2Connection(socket, route, readTimeout, writeTimeout, h2InitialWindowSize, h2MaxFrameSize, h2BufferSize); } else { return new H1Connection(socket, route, readTimeout); } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java index b652b999e..fbe650d76 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java @@ -181,7 +181,8 @@ public final class HttpConnectionPool implements ConnectionPool { dnsResolver, builder.socketFactory, builder.h2InitialWindowSize, - builder.h2MaxFrameSize); + builder.h2MaxFrameSize, + builder.h2BufferSize); this.h1Manager = new H1ConnectionManager(this.maxIdleTimeNanos); this.connectionPermits = new Semaphore(builder.maxTotalConnections, false); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java index 59281ad1e..0c67d25a7 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java @@ -26,6 +26,7 @@ public final class HttpConnectionPoolBuilder { int h2StreamsPerConnection = 100; int h2InitialWindowSize = 65535; // RFC 9113 default int h2MaxFrameSize = 16384; // RFC 9113 default + int h2BufferSize = 256 * 1024; // 256KB default final Map perHostLimits = new HashMap<>(); Duration maxIdleTime = Duration.ofMinutes(2); @@ -483,6 +484,28 @@ public HttpConnectionPoolBuilder h2StreamsPerConnection(int streams) { return this; } + /** + * Set HTTP/2 I/O buffer size (default: 256KB). + * + *

      This controls the size of the buffered input and output streams used for + * reading and writing HTTP/2 frames. Larger buffers reduce syscall overhead + * and improve throughput for large payloads. + * + *

      Memory impact: Each HTTP/2 connection uses 2× this value (input + output). + * With 100 connections at 256KB, total buffer memory is ~50MB. + * + * @param bufferSize buffer size in bytes, must be at least 16KB + * @return this builder + * @throws IllegalArgumentException if bufferSize is less than 16KB + */ + public HttpConnectionPoolBuilder h2BufferSize(int bufferSize) { + if (bufferSize < 16 * 1024) { + throw new IllegalArgumentException("h2BufferSize must be at least 16KB: " + bufferSize); + } + this.h2BufferSize = bufferSize; + return this; + } + /** * Add a listener for connection pool lifecycle events. * diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java index 35fc2bbee..d085b27a6 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java @@ -57,7 +57,6 @@ static Socket defaultSocketFactory(Route route, List endpoints) thr Socket socket = new Socket(); socket.setTcpNoDelay(true); socket.setKeepAlive(true); - // Larger buffers for high throughput socket.setSendBufferSize(64 * 1024); socket.setReceiveBufferSize(64 * 1024); return socket; diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java index 39db3fa00..a3a83f520 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java @@ -16,6 +16,9 @@ */ final class FlowControlWindow { + // Poll interval for timeout checking (avoids ScheduledThreadPoolExecutor contention) + private static final long POLL_INTERVAL_NS = TimeUnit.MILLISECONDS.toNanos(10); + private final ReentrantLock lock = new ReentrantLock(); private final Condition available = lock.newCondition(); private long window; @@ -53,11 +56,12 @@ int tryAcquireNonBlocking(int maxBytes) { * Try to acquire up to the requested bytes from the window. * *

      This method acquires as many bytes as available (up to the requested amount), - * waiting only if the window is completely empty. + * waiting only if the window is completely empty. Uses short polling intervals + * to avoid contention on the ScheduledThreadPoolExecutor used for long timed waits. * * @param maxBytes maximum number of bytes to acquire - * @param timeoutMs maximum time to wait in milliseconds (only if window is empty) - * @return number of bytes acquired (0 if timeout expired with empty window) + * @param timeoutMs maximum time to wait in milliseconds + * @return number of bytes acquired (0 if timeout expired) * @throws InterruptedException if interrupted while waiting */ int tryAcquireUpTo(int maxBytes, long timeoutMs) throws InterruptedException { @@ -70,13 +74,17 @@ int tryAcquireUpTo(int maxBytes, long timeoutMs) throws InterruptedException { return acquired; } - // Slow path: wait for any capacity + // Slow path: poll with short intervals to avoid timed-wait contention long remainingNs = TimeUnit.MILLISECONDS.toNanos(timeoutMs); while (window <= 0) { if (remainingNs <= 0) { - return 0; + return 0; // Timeout } - remainingNs = available.awaitNanos(remainingNs); + // Use short poll interval instead of full timeout + long waitNs = Math.min(remainingNs, POLL_INTERVAL_NS); + long before = System.nanoTime(); + available.awaitNanos(waitNs); + remainingNs -= (System.nanoTime() - before); } int acquired = (int) Math.min(window, maxBytes); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java index d86695aa6..b81560783 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java @@ -85,7 +85,6 @@ private enum State { } private static final InternalLogger LOGGER = InternalLogger.getLogger(H2Connection.class); - private static final int BUFFER_SIZE = 65536; private static final int SETTINGS_TIMEOUT_MS = 10_000; private static final int GRACEFUL_SHUTDOWN_MS = 1000; @@ -126,6 +125,7 @@ private enum State { * @param writeTimeout write timeout duration * @param initialWindowSize initial flow control window size in bytes * @param maxFrameSize maximum frame size to advertise to server + * @param bufferSize I/O buffer size in bytes */ public H2Connection( Socket socket, @@ -133,12 +133,13 @@ public H2Connection( Duration readTimeout, Duration writeTimeout, int initialWindowSize, - int maxFrameSize + int maxFrameSize, + int bufferSize ) throws IOException { this.socket = socket; this.maxFrameSize = maxFrameSize; - var socketIn = new UnsyncBufferedInputStream(socket.getInputStream(), BUFFER_SIZE); - var socketOut = new UnsyncBufferedOutputStream(socket.getOutputStream(), BUFFER_SIZE); + var socketIn = new UnsyncBufferedInputStream(socket.getInputStream(), bufferSize); + var socketOut = new UnsyncBufferedOutputStream(socket.getOutputStream(), bufferSize); this.route = route; this.readTimeoutMs = readTimeout.toMillis(); this.writeTimeoutMs = writeTimeout.toMillis(); @@ -158,6 +159,8 @@ public H2Connection( try { sendConnectionPreface(); receiveServerPreface(); + // Try to receive initial connection WINDOW_UPDATE (server often sends this right after SETTINGS) + receiveInitialWindowUpdate(); } catch (IOException e) { close(); throw new IOException("HTTP/2 connection preface failed", e); @@ -498,6 +501,29 @@ private void receiveServerPreface() throws IOException { } } + /** + * Try to receive the initial connection-level WINDOW_UPDATE that servers typically send + * right after SETTINGS to expand the connection flow control window. + * Uses a short timeout - if no frame arrives quickly, we proceed anyway. + */ + private void receiveInitialWindowUpdate() throws IOException { + int originalTimeout = socket.getSoTimeout(); + try { + socket.setSoTimeout(50); // Short timeout - don't block long if server doesn't send one + + int type = frameCodec.nextFrame(); + if (type == FRAME_TYPE_WINDOW_UPDATE && frameCodec.frameStreamId() == 0) { + int increment = frameCodec.readAndParseWindowUpdate(); + muxer.releaseConnectionWindow(increment); + } + // If it's any other frame type, it will be processed by the reader thread + } catch (SocketTimeoutException e) { + // No initial WINDOW_UPDATE - that's fine, proceed with default window + } finally { + socket.setSoTimeout(originalTimeout); + } + } + private void applyRemoteSettings(int[] settings) throws IOException { for (int i = 0; i < settings.length; i += 2) { int id = settings[i]; diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java index e9eeaa9b5..19b994d82 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java @@ -214,6 +214,12 @@ public long transferTo(OutputStream out) throws IOException { readPosition = currentLength; } + // Return the last buffer (pullNextChunk returned false but buffer may still be set) + if (currentBuffer != null) { + bufferReturner.accept(currentBuffer); + currentBuffer = null; + } + return transferred; } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index 6472ad0d4..62c4e8f2d 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -111,10 +111,6 @@ public final class H2Exchange implements HttpExchange { private volatile int readDeadlineTick; // 0 = no deadline, >0 = deadline tick private final AtomicBoolean readTimedOut = new AtomicBoolean(); // At-most-once timeout flag - // Adaptive signaling: batch signals for fast transfers, but don't delay small/slow responses - // Signal when: queue was empty (first chunk), threshold reached, or endStream - private static final int SIGNAL_THRESHOLD = 4; // Signal when this many chunks are queued - // Response headers (status code is in packedState) private volatile HttpHeaders responseHeaders; @@ -418,14 +414,11 @@ void signalStreamError(H2Exception error) { * used to defer signaling when processing a burst of frames */ void enqueueData(byte[] data, int length, boolean endStream, boolean moreDataBuffered) { - boolean shouldSignal; boolean sendWindowUpdate = false; int windowIncrement = 0; dataLock.lock(); try { - int queueSizeBefore = dataQueue.size(); - if (data != null && length > 0) { dataQueue.add(new DataChunk(data, length, endStream)); @@ -448,22 +441,6 @@ void enqueueData(byte[] data, int length, boolean endStream, boolean moreDataBuf state.setEndStreamReceivedFlag(); // Just set flag + readState, don't update stream state clearReadDeadline(); // No more data expected, clear timeout } - - // Pipelined adaptive signaling: balance batching with parallelism. - // With lock-free signaling, wakeup cost is low enough to prioritize parallelism. - // Signal if: - // 1. endStream - response complete - // 2. threshold reached - safety valve for huge buffers - // 3. queueWasEmpty - TTFB optimization, enables consumer to process in parallel - // 4. buffer empty - we've consumed everything the kernel gave us - int queueSize = dataQueue.size(); - boolean queueWasEmpty = queueSizeBefore == 0 && queueSize > 0; - boolean thresholdReached = queueSize >= SIGNAL_THRESHOLD; - - shouldSignal = endStream - || thresholdReached - || queueWasEmpty - || !moreDataBuffered; } finally { dataLock.unlock(); } @@ -473,8 +450,12 @@ void enqueueData(byte[] data, int length, boolean endStream, boolean moreDataBuf muxer.queueControlFrame(streamId, H2Muxer.ControlFrameType.WINDOW_UPDATE, windowIncrement, writeTimeoutMs); } - // Signal outside lock - lock-free wakeup - if (shouldSignal) { + // Signal consumer only when necessary to reduce wakeup overhead: + // - endStream: response complete, consumer must finish + // - !moreDataBuffered: no more data in socket buffer, signal now before reader blocks + // When moreDataBuffered=true, defer signaling - H2Connection will call signalDataAvailable() + // when switching streams or when buffer empties. + if (endStream || !moreDataBuffered) { Thread t = waitingThread; if (t != null) { LockSupport.unpark(t); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java index f89e426f7..95716e382 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2FrameCodec.java @@ -54,17 +54,13 @@ final class H2FrameCodec { private final int maxFrameSize; // Write header buffer - used by writer thread only. - // Read operations use zero-copy direct buffer access from UnsyncBufferedInputStream. private final byte[] writeHeaderBuf = new byte[FRAME_HEADER_SIZE]; // Scratch buffer for writing control frames - writer thread only. - // Used for WINDOW_UPDATE(4), RST_STREAM(4), and GOAWAY(8+) payloads. - // Read operations use zero-copy direct buffer access from UnsyncBufferedInputStream. private static final int WRITE_SCRATCH_SIZE = 64; private final byte[] writeScratch = new byte[WRITE_SCRATCH_SIZE]; // Reusable buffer for accumulating header blocks when CONTINUATION frames are needed. - // Reader thread only. Reset and reused across requests to avoid repeated allocations. private final ByteBufferOutputStream headerBlockBuffer = new ByteBufferOutputStream(4096); // Current frame state (filled by nextFrame()) - stateful parser pattern diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java index 84f1c4b33..f9d21e46d 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java @@ -96,13 +96,15 @@ enum ControlFrameType { private static final class SendWindowWaiter { final Thread thread; final int maxBytes; + final long deadlineNs; volatile int acquired; volatile boolean done; volatile boolean cancelled; - SendWindowWaiter(Thread thread, int maxBytes) { + SendWindowWaiter(Thread thread, int maxBytes, long deadlineNs) { this.thread = thread; this.maxBytes = maxBytes; + this.deadlineNs = deadlineNs; } } @@ -326,16 +328,16 @@ int acquireConnectionWindowUpTo(int maxBytes, long timeoutMs) throws SocketTimeo } // Slow path: queue and wait for fair access - var waiter = new SendWindowWaiter(Thread.currentThread(), maxBytes); + long deadlineNs = System.nanoTime() + timeoutMs * 1_000_000L; + var waiter = new SendWindowWaiter(Thread.currentThread(), maxBytes, deadlineNs); sendWindowWaiters.add(waiter); try { - int deadlineTick = deadlineTick(timeoutMs); while (!waiter.done) { - if (deadlineTick > 0 && timeoutTick >= deadlineTick) { + if (System.nanoTime() >= deadlineNs) { return 0; // Timeout } - LockSupport.parkNanos(TIMEOUT_POLL_INTERVAL_MS * 1_000_000L); + LockSupport.park(); // Untimed - woken by wakeWaiters() or watchdog if (Thread.interrupted()) { throw new InterruptedException(); } @@ -379,6 +381,18 @@ private void wakeWaiters() { } } + /** + * Wake waiters that have timed out so they can check their deadline. + */ + private void wakeTimedOutWaiters() { + long now = System.nanoTime(); + for (SendWindowWaiter waiter : sendWindowWaiters) { + if (!waiter.done && !waiter.cancelled && now >= waiter.deadlineNs) { + LockSupport.unpark(waiter.thread); + } + } + } + // ==================== WRITE QUEUE ==================== void signalDataReady(H2Exchange exchange) { @@ -620,6 +634,7 @@ private void workerLoop() { int tick = ++timeoutTick; checkReadTimeouts(tick); checkWriteTimeouts(tick); + wakeTimedOutWaiters(); lastTimeoutCheck = now; } @@ -644,13 +659,7 @@ private void processExchangePendingWrites(H2Exchange exchange) { while ((pw = exchange.pendingWrites.poll()) != null) { byte[] buffer = pw.data; try { - frameCodec.writeFrame( - FRAME_TYPE_DATA, - pw.flags, - streamId, - pw.data, - pw.offset, - pw.length); + frameCodec.writeFrame(FRAME_TYPE_DATA, pw.flags, streamId, pw.data, pw.offset, pw.length); } catch (IOException e) { exchange.returnBuffer(buffer); failWriter(e); From ed6c99556b1da6269c59093927682559bccfd027 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 5 Feb 2026 11:34:10 -0600 Subject: [PATCH 44/60] Dry up integ tests --- .../http/client/it/RequestResponseTest.java | 141 ++++++++++++++++++ .../http/client/it/RequestStreamingTest.java | 118 +++++++++++++++ .../java/http/client/it/TransportConfig.java | 53 +++++++ .../it/h1/PerRouteLimitsHttp11Test.java | 6 +- .../it/h1/RequestResponseHttp11ClearTest.java | 51 ------- .../h1/RequestResponseHttp11WithTlsTest.java | 76 ---------- .../client/it/h1/StatusCodesHttp11Test.java | 76 ++-------- .../it/h2/RequestResponseHttp2ClearTest.java | 56 ------- .../h2/RequestResponseHttp2WithAlpnTest.java | 81 ---------- .../h2/RequestResponseHttp2WithTlsTest.java | 81 ---------- .../it/h2/RequestStreamingHttp2ClearTest.java | 57 ------- .../h2/RequestStreamingHttp2WithAlpnTest.java | 81 ---------- .../h2/RequestStreamingHttp2WithTlsTest.java | 81 ---------- .../connection/H2ConnectionManager.java | 7 +- .../connection/HttpConnectionFactory.java | 8 +- 15 files changed, 340 insertions(+), 633 deletions(-) create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseTest.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingTest.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TransportConfig.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11ClearTest.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11WithTlsTest.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2ClearTest.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithAlpnTest.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithTlsTest.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2ClearTest.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithAlpnTest.java delete mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithTlsTest.java diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseTest.java new file mode 100644 index 000000000..77969a19f --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestResponseTest.java @@ -0,0 +1,141 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import javax.net.ssl.SSLContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; +import software.amazon.smithy.java.http.client.it.server.h1.MultiplexingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.RequestCapturingHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.MultiplexingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.RequestCapturingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; + +/** + * Parameterized test for basic request/response across all transport configurations. + */ +public class RequestResponseTest { + + private static final String RESPONSE_CONTENTS = "Test response body"; + private static final String REQUEST_CONTENTS = "Test request body"; + + private static TestCertificateGenerator.CertificateBundle certBundle; + private static SSLContext clientSslContext; + + private NettyTestServer server; + private HttpClient client; + private RequestCapturingHttp2ClientHandler h2RequestHandler; + private RequestCapturingHttp11ClientHandler h1RequestHandler; + + @BeforeAll + static void beforeAll() throws Exception { + certBundle = TestCertificateGenerator.generateCertificates(); + clientSslContext = TestUtils.createClientSslContext(certBundle); + } + + @BeforeEach + void setUp(TestInfo testInfo) { + // Setup is done in the test method based on config + } + + @AfterEach + void tearDown() throws Exception { + if (client != null) { + client.close(); + } + if (server != null) { + server.stop(); + } + } + + private void setupForConfig(TransportConfig config) throws Exception { + var serverBuilder = NettyTestServer.builder().httpVersion(config.httpVersion()); + + if (config.isHttp2()) { + h2RequestHandler = new RequestCapturingHttp2ClientHandler(); + serverBuilder.h2ConnectionMode(config.h2Mode()) + .http2HandlerFactory(ctx -> new MultiplexingHttp2ClientHandler( + h2RequestHandler, + new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS))); + } else { + h1RequestHandler = new RequestCapturingHttp11ClientHandler(); + serverBuilder.http11HandlerFactory(ctx -> new MultiplexingHttp11ClientHandler( + h1RequestHandler, + new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS))); + } + + if (config.useTls()) { + serverBuilder.sslContextBuilder(TestUtils.createServerSslContextBuilder(certBundle)); + } + + server = serverBuilder.build(); + server.start(); + + var poolBuilder = HttpConnectionPool.builder() + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .dnsResolver(DnsResolver.staticMapping(Map.of("localhost", List.of(InetAddress.getLoopbackAddress())))) + .httpVersionPolicy(config.versionPolicy()); + + if (config.useTls()) { + poolBuilder.sslContext(clientSslContext); + } + + client = HttpClient.builder().connectionPool(poolBuilder.build()).build(); + } + + private String uri(TransportConfig config) { + String scheme = config.useTls() ? "https" : "http"; + return scheme + "://localhost:" + server.getPort(); + } + + private String readBody(HttpResponse response) throws IOException { + try (var body = response.body().asInputStream()) { + return new String(body.readAllBytes(), StandardCharsets.UTF_8); + } + } + + @ParameterizedTest(name = "{0}") + @EnumSource(TransportConfig.class) + void canSendRequestAndReadResponse(TransportConfig config) throws Exception { + setupForConfig(config); + + var request = TestUtils.plainTextRequest(config.httpVersion(), uri(config), REQUEST_CONTENTS); + var response = client.send(request); + var responseBody = readBody(response); + + String capturedBody; + if (config.isHttp2()) { + h2RequestHandler.streamCompleted().join(); + capturedBody = h2RequestHandler.capturedBody().toString(StandardCharsets.UTF_8); + } else { + capturedBody = h1RequestHandler.capturedBody().toString(StandardCharsets.UTF_8); + } + + assertEquals(REQUEST_CONTENTS, capturedBody); + assertEquals(RESPONSE_CONTENTS, responseBody); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingTest.java new file mode 100644 index 000000000..661516fef --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/RequestStreamingTest.java @@ -0,0 +1,118 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.smithy.java.http.client.it.TestUtils.IPSUM_LOREM; +import static software.amazon.smithy.java.http.client.it.TestUtils.streamingBody; + +import java.io.IOException; +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import javax.net.ssl.SSLContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.http.client.dns.DnsResolver; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; +import software.amazon.smithy.java.http.client.it.server.h2.MultiplexingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.RequestCapturingHttp2ClientHandler; +import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; + +/** + * Parameterized test for streaming request body across HTTP/2 transport configurations. + */ +public class RequestStreamingTest { + + private static final String RESPONSE_CONTENTS = "Test response body"; + + private static TestCertificateGenerator.CertificateBundle certBundle; + private static SSLContext clientSslContext; + + private NettyTestServer server; + private HttpClient client; + private RequestCapturingHttp2ClientHandler requestHandler; + + @BeforeAll + static void beforeAll() throws Exception { + certBundle = TestCertificateGenerator.generateCertificates(); + clientSslContext = TestUtils.createClientSslContext(certBundle); + } + + @AfterEach + void tearDown() throws Exception { + if (client != null) { + client.close(); + } + if (server != null) { + server.stop(); + } + } + + private void setupForConfig(TransportConfig config) throws Exception { + requestHandler = new RequestCapturingHttp2ClientHandler(); + + var serverBuilder = NettyTestServer.builder() + .httpVersion(config.httpVersion()) + .h2ConnectionMode(config.h2Mode()) + .http2HandlerFactory(ctx -> new MultiplexingHttp2ClientHandler( + requestHandler, + new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS))); + + if (config.useTls()) { + serverBuilder.sslContextBuilder(TestUtils.createServerSslContextBuilder(certBundle)); + } + + server = serverBuilder.build(); + server.start(); + + var poolBuilder = HttpConnectionPool.builder() + .maxConnectionsPerRoute(10) + .maxTotalConnections(10) + .maxIdleTime(Duration.ofMinutes(1)) + .dnsResolver(DnsResolver.staticMapping(Map.of("localhost", List.of(InetAddress.getLoopbackAddress())))) + .httpVersionPolicy(config.versionPolicy()); + + if (config.useTls()) { + poolBuilder.sslContext(clientSslContext); + } + + client = HttpClient.builder().connectionPool(poolBuilder.build()).build(); + } + + private String uri(TransportConfig config) { + String scheme = config.useTls() ? "https" : "http"; + return scheme + "://localhost:" + server.getPort(); + } + + private String readBody(HttpResponse response) throws IOException { + try (var body = response.body().asInputStream()) { + return new String(body.readAllBytes(), StandardCharsets.UTF_8); + } + } + + @ParameterizedTest(name = "{0}") + @EnumSource(value = TransportConfig.class, names = {"H2C", "H2_TLS", "H2_ALPN"}) + void canSendStreamingRequestAndReadResponse(TransportConfig config) throws Exception { + setupForConfig(config); + + var request = TestUtils.request(config.httpVersion(), uri(config), streamingBody(IPSUM_LOREM)); + var response = client.send(request); + + requestHandler.streamCompleted().join(); + + assertEquals(String.join("", IPSUM_LOREM), requestHandler.capturedBody().toString(StandardCharsets.UTF_8)); + assertEquals(RESPONSE_CONTENTS, readBody(response)); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TransportConfig.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TransportConfig.java new file mode 100644 index 000000000..d34d8b6f0 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/TransportConfig.java @@ -0,0 +1,53 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it; + +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer.H2ConnectionMode; + +/** + * Transport configurations for parameterized HTTP client tests. + */ +public enum TransportConfig { + H1_CLEAR(HttpVersion.HTTP_1_1, false, null, HttpVersionPolicy.ENFORCE_HTTP_1_1), + H1_TLS(HttpVersion.HTTP_1_1, true, null, HttpVersionPolicy.ENFORCE_HTTP_1_1), + H2C(HttpVersion.HTTP_2, false, H2ConnectionMode.PRIOR_KNOWLEDGE, HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE), + H2_TLS(HttpVersion.HTTP_2, true, H2ConnectionMode.PRIOR_KNOWLEDGE, HttpVersionPolicy.ENFORCE_HTTP_2), + H2_ALPN(HttpVersion.HTTP_2, true, H2ConnectionMode.ALPN, HttpVersionPolicy.AUTOMATIC); + + private final HttpVersion httpVersion; + private final boolean useTls; + private final H2ConnectionMode h2Mode; + private final HttpVersionPolicy versionPolicy; + + TransportConfig(HttpVersion httpVersion, boolean useTls, H2ConnectionMode h2Mode, HttpVersionPolicy versionPolicy) { + this.httpVersion = httpVersion; + this.useTls = useTls; + this.h2Mode = h2Mode; + this.versionPolicy = versionPolicy; + } + + public HttpVersion httpVersion() { + return httpVersion; + } + + public boolean useTls() { + return useTls; + } + + public H2ConnectionMode h2Mode() { + return h2Mode; + } + + public HttpVersionPolicy versionPolicy() { + return versionPolicy; + } + + public boolean isHttp2() { + return httpVersion == HttpVersion.HTTP_2; + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/PerRouteLimitsHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/PerRouteLimitsHttp11Test.java index 1f3bc885c..66a0a2a89 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/PerRouteLimitsHttp11Test.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/PerRouteLimitsHttp11Test.java @@ -8,12 +8,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.net.InetAddress; +import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.List; import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpResponse; import software.amazon.smithy.java.http.api.HttpVersion; import software.amazon.smithy.java.http.client.HttpClient; import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; @@ -102,10 +104,10 @@ void differentRoutesHaveIndependentLimits() throws Exception { assertEquals(1, handler2.connectionCount()); } - private String readBody(software.amazon.smithy.java.http.api.HttpResponse response) { + private String readBody(HttpResponse response) { var buf = response.body().asByteBuffer(); var bytes = new byte[buf.remaining()]; buf.get(bytes); - return new String(bytes, java.nio.charset.StandardCharsets.UTF_8); + return new String(bytes, StandardCharsets.UTF_8); } } diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11ClearTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11ClearTest.java deleted file mode 100644 index deeb0f721..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11ClearTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it.h1; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.h1.MultiplexingHttp11ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h1.RequestCapturingHttp11ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; - -/** - * Tests basic HTTP/1.1 request/response over cleartext (no TLS). - */ -public class RequestResponseHttp11ClearTest extends BaseHttpClientIntegTest { - - private RequestCapturingHttp11ClientHandler requestCapturingHandler; - - @Override - protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { - requestCapturingHandler = new RequestCapturingHttp11ClientHandler(); - return builder - .httpVersion(HttpVersion.HTTP_1_1) - .http11HandlerFactory(ctx -> new MultiplexingHttp11ClientHandler( - requestCapturingHandler, - new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS))); - } - - @Override - protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { - return builder.httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1); - } - - @Test - void canSendRequestAndReadResponse() throws Exception { - var request = plainTextRequest(HttpVersion.HTTP_1_1, REQUEST_CONTENTS); - - var response = client.send(request); - - assertEquals(REQUEST_CONTENTS, requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); - assertEquals(RESPONSE_CONTENTS, readBody(response)); - } -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11WithTlsTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11WithTlsTest.java deleted file mode 100644 index 4eba073c6..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/RequestResponseHttp11WithTlsTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it.h1; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.it.TestUtils; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; -import software.amazon.smithy.java.http.client.it.server.h1.MultiplexingHttp11ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h1.RequestCapturingHttp11ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h1.TextResponseHttp11ClientHandler; - -/** - * Tests basic HTTP/1.1 request/response over TLS. - */ -public class RequestResponseHttp11WithTlsTest extends BaseHttpClientIntegTest { - - private static TestCertificateGenerator.CertificateBundle bundle; - private RequestCapturingHttp11ClientHandler requestCapturingHandler; - - @BeforeAll - static void beforeAll() throws Exception { - bundle = TestCertificateGenerator.generateCertificates(); - } - - @Override - protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { - requestCapturingHandler = new RequestCapturingHttp11ClientHandler(); - try { - return builder - .httpVersion(HttpVersion.HTTP_1_1) - .http11HandlerFactory(ctx -> new MultiplexingHttp11ClientHandler( - requestCapturingHandler, - new TextResponseHttp11ClientHandler(RESPONSE_CONTENTS))) - .sslContextBuilder(TestUtils.createServerSslContextBuilder(bundle)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { - try { - return builder - .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) - .sslContext(TestUtils.createClientSslContext(bundle)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - protected String uri() { - return "https://localhost:" + server.getPort(); - } - - @Test - void canSendRequestAndReadResponse() throws Exception { - var request = plainTextRequest(HttpVersion.HTTP_1_1, REQUEST_CONTENTS); - - var response = client.send(request); - - assertEquals(REQUEST_CONTENTS, requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); - assertEquals(RESPONSE_CONTENTS, readBody(response)); - } -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/StatusCodesHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/StatusCodesHttp11Test.java index 4b1e10e55..bceeb5589 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/StatusCodesHttp11Test.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/StatusCodesHttp11Test.java @@ -8,13 +8,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.net.InetAddress; -import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.List; import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import software.amazon.smithy.java.http.api.HttpVersion; import software.amazon.smithy.java.http.client.HttpClient; import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; @@ -25,7 +25,7 @@ import software.amazon.smithy.java.http.client.it.server.h1.StatusCodeHttp11ClientHandler; /** - * Tests various HTTP status codes with empty bodies. + * Tests various HTTP status codes. */ public class StatusCodesHttp11Test { @@ -34,17 +34,15 @@ public class StatusCodesHttp11Test { @BeforeEach void setUp() { - DnsResolver staticDns = DnsResolver.staticMapping(Map.of( - "localhost", - List.of(InetAddress.getLoopbackAddress()))); - client = HttpClient.builder() .connectionPool(HttpConnectionPool.builder() .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) .maxConnectionsPerRoute(10) .maxTotalConnections(10) .maxIdleTime(Duration.ofMinutes(1)) - .dnsResolver(staticDns) + .dnsResolver(DnsResolver.staticMapping(Map.of( + "localhost", + List.of(InetAddress.getLoopbackAddress())))) .build()) .build(); } @@ -59,66 +57,18 @@ void tearDown() throws Exception { } } - @Test - void handles200WithEmptyBody() throws Exception { - startServer(200); - var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, "http://localhost:" + server.getPort(), ""); - var response = client.send(request); - - assertEquals(200, response.statusCode()); - assertEquals("", readBody(response)); - } - - @Test - void handles204NoContent() throws Exception { - startServer(204); - var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, "http://localhost:" + server.getPort(), ""); - var response = client.send(request); - - assertEquals(204, response.statusCode()); - assertEquals("", readBody(response)); - } - - @Test - void handles304NotModified() throws Exception { - startServer(304); - var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, "http://localhost:" + server.getPort(), ""); - var response = client.send(request); - - assertEquals(304, response.statusCode()); - assertEquals("", readBody(response)); - } - - @Test - void handles404NotFound() throws Exception { - startServer(404); - var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, "http://localhost:" + server.getPort(), ""); - var response = client.send(request); - - assertEquals(404, response.statusCode()); - } - - @Test - void handles500InternalServerError() throws Exception { - startServer(500); - var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, "http://localhost:" + server.getPort(), ""); - var response = client.send(request); - - assertEquals(500, response.statusCode()); - } - - private void startServer(int statusCode) throws Exception { + @ParameterizedTest(name = "status {0}") + @ValueSource(ints = {200, 204, 304, 404, 500}) + void handlesStatusCode(int statusCode) throws Exception { server = NettyTestServer.builder() .httpVersion(HttpVersion.HTTP_1_1) .http11HandlerFactory(ctx -> new StatusCodeHttp11ClientHandler(statusCode)) .build(); server.start(); - } - private String readBody(software.amazon.smithy.java.http.api.HttpResponse response) { - var buf = response.body().asByteBuffer(); - var bytes = new byte[buf.remaining()]; - buf.get(bytes); - return new String(bytes, StandardCharsets.UTF_8); + var request = TestUtils.plainTextRequest(HttpVersion.HTTP_1_1, "http://localhost:" + server.getPort(), ""); + var response = client.send(request); + + assertEquals(statusCode, response.statusCode()); } } diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2ClearTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2ClearTest.java deleted file mode 100644 index 2b01b4396..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2ClearTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it.h2; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.h2.MultiplexingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h2.RequestCapturingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; - -/** - * Tests basic HTTP/2 request/response over cleartext (h2c with prior knowledge). - */ -public class RequestResponseHttp2ClearTest extends BaseHttpClientIntegTest { - - private RequestCapturingHttp2ClientHandler requestCapturingHandler; - - @Override - protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { - requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); - return builder - .httpVersion(HttpVersion.HTTP_2) - .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) - .http2HandlerFactory(ctx -> new MultiplexingHttp2ClientHandler( - requestCapturingHandler, - new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS))); - } - - @Override - protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { - return builder.httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE); - } - - @Test - void canSendRequestAndReadResponse() throws Exception { - var request = plainTextRequest(HttpVersion.HTTP_2, REQUEST_CONTENTS); - - var response = client.send(request); - var responseBody = readBody(response); - - // Wait for server to receive all request data - requestCapturingHandler.streamCompleted().join(); - - assertEquals(REQUEST_CONTENTS, requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); - assertEquals(RESPONSE_CONTENTS, responseBody); - } -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithAlpnTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithAlpnTest.java deleted file mode 100644 index 9110295e7..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithAlpnTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it.h2; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.it.TestUtils; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; -import software.amazon.smithy.java.http.client.it.server.h2.MultiplexingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h2.RequestCapturingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; - -/** - * Tests basic HTTP/2 request/response over TLS with ALPN negotiation. - */ -public class RequestResponseHttp2WithAlpnTest extends BaseHttpClientIntegTest { - - private static TestCertificateGenerator.CertificateBundle bundle; - private RequestCapturingHttp2ClientHandler requestCapturingHandler; - - @BeforeAll - static void beforeAll() throws Exception { - bundle = TestCertificateGenerator.generateCertificates(); - } - - @Override - protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { - requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); - try { - return builder - .httpVersion(HttpVersion.HTTP_2) - .h2ConnectionMode(NettyTestServer.H2ConnectionMode.ALPN) - .http2HandlerFactory(ctx -> new MultiplexingHttp2ClientHandler( - requestCapturingHandler, - new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS))) - .sslContextBuilder(TestUtils.createServerSslContextBuilder(bundle)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { - try { - return builder - .httpVersionPolicy(HttpVersionPolicy.AUTOMATIC) - .sslContext(TestUtils.createClientSslContext(bundle)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - protected String uri() { - return "https://localhost:" + server.getPort(); - } - - @Test - void canSendRequestAndReadResponse() throws Exception { - var request = plainTextRequest(HttpVersion.HTTP_2, REQUEST_CONTENTS); - - var response = client.send(request); - var responseBody = readBody(response); - - // Wait for server to receive all request data - requestCapturingHandler.streamCompleted().join(); - - assertEquals(REQUEST_CONTENTS, requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); - assertEquals(RESPONSE_CONTENTS, responseBody); - } -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithTlsTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithTlsTest.java deleted file mode 100644 index f37778525..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestResponseHttp2WithTlsTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it.h2; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.it.TestUtils; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; -import software.amazon.smithy.java.http.client.it.server.h2.MultiplexingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h2.RequestCapturingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; - -/** - * Tests basic HTTP/2 request/response over TLS with prior knowledge (no ALPN). - */ -public class RequestResponseHttp2WithTlsTest extends BaseHttpClientIntegTest { - - private static TestCertificateGenerator.CertificateBundle bundle; - private RequestCapturingHttp2ClientHandler requestCapturingHandler; - - @BeforeAll - static void beforeAll() throws Exception { - bundle = TestCertificateGenerator.generateCertificates(); - } - - @Override - protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { - requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); - try { - return builder - .httpVersion(HttpVersion.HTTP_2) - .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) - .http2HandlerFactory(ctx -> new MultiplexingHttp2ClientHandler( - requestCapturingHandler, - new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS))) - .sslContextBuilder(TestUtils.createServerSslContextBuilder(bundle)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { - try { - return builder - .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_2) - .sslContext(TestUtils.createClientSslContext(bundle)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - protected String uri() { - return "https://localhost:" + server.getPort(); - } - - @Test - void canSendRequestAndReadResponse() throws Exception { - var request = plainTextRequest(HttpVersion.HTTP_2, REQUEST_CONTENTS); - - var response = client.send(request); - var responseBody = readBody(response); - - // Wait for server to receive all request data - requestCapturingHandler.streamCompleted().join(); - - assertEquals(REQUEST_CONTENTS, requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); - assertEquals(RESPONSE_CONTENTS, responseBody); - } -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2ClearTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2ClearTest.java deleted file mode 100644 index a3670afbb..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2ClearTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it.h2; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static software.amazon.smithy.java.http.client.it.TestUtils.IPSUM_LOREM; -import static software.amazon.smithy.java.http.client.it.TestUtils.streamingBody; - -import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.it.TestUtils; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.h2.MultiplexingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h2.RequestCapturingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; - -/** - * Tests HTTP/2 streaming request body over cleartext (h2c with prior knowledge). - */ -public class RequestStreamingHttp2ClearTest extends BaseHttpClientIntegTest { - - private RequestCapturingHttp2ClientHandler requestCapturingHandler; - - @Override - protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { - requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); - return builder - .httpVersion(HttpVersion.HTTP_2) - .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) - .http2HandlerFactory(ctx -> new MultiplexingHttp2ClientHandler( - requestCapturingHandler, - new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS))); - } - - @Override - protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { - return builder.httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE); - } - - @Test - void canSendStreamingRequestAndReadResponse() throws Exception { - var request = TestUtils.request(HttpVersion.HTTP_2, uri(), streamingBody(IPSUM_LOREM)); - - var response = client.send(request); - requestCapturingHandler.streamCompleted().join(); - - assertEquals(String.join("", IPSUM_LOREM), - requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); - assertEquals(RESPONSE_CONTENTS, readBody(response)); - } -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithAlpnTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithAlpnTest.java deleted file mode 100644 index 12118c9fe..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithAlpnTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it.h2; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static software.amazon.smithy.java.http.client.it.TestUtils.IPSUM_LOREM; -import static software.amazon.smithy.java.http.client.it.TestUtils.streamingBody; - -import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.it.TestUtils; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; -import software.amazon.smithy.java.http.client.it.server.h2.MultiplexingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h2.RequestCapturingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; - -/** - * Tests HTTP/2 streaming request body over TLS with ALPN negotiation. - */ -public class RequestStreamingHttp2WithAlpnTest extends BaseHttpClientIntegTest { - - private static TestCertificateGenerator.CertificateBundle bundle; - private RequestCapturingHttp2ClientHandler requestCapturingHandler; - - @BeforeAll - static void beforeAll() throws Exception { - bundle = TestCertificateGenerator.generateCertificates(); - } - - @Override - protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { - requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); - try { - return builder - .httpVersion(HttpVersion.HTTP_2) - .h2ConnectionMode(NettyTestServer.H2ConnectionMode.ALPN) - .http2HandlerFactory(ctx -> new MultiplexingHttp2ClientHandler( - requestCapturingHandler, - new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS))) - .sslContextBuilder(TestUtils.createServerSslContextBuilder(bundle)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { - try { - return builder - .httpVersionPolicy(HttpVersionPolicy.AUTOMATIC) - .sslContext(TestUtils.createClientSslContext(bundle)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - protected String uri() { - return "https://localhost:" + server.getPort(); - } - - @Test - void canSendStreamingRequestAndReadResponse() throws Exception { - var request = TestUtils.request(HttpVersion.HTTP_2, uri(), streamingBody(IPSUM_LOREM)); - - var response = client.send(request); - requestCapturingHandler.streamCompleted().join(); - - assertEquals(String.join("", IPSUM_LOREM), - requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); - assertEquals(RESPONSE_CONTENTS, readBody(response)); - } -} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithTlsTest.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithTlsTest.java deleted file mode 100644 index 4cc38df0c..000000000 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/RequestStreamingHttp2WithTlsTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.it.h2; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static software.amazon.smithy.java.http.client.it.TestUtils.IPSUM_LOREM; -import static software.amazon.smithy.java.http.client.it.TestUtils.streamingBody; - -import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpVersion; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; -import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; -import software.amazon.smithy.java.http.client.it.TestUtils; -import software.amazon.smithy.java.http.client.it.server.NettyTestServer; -import software.amazon.smithy.java.http.client.it.server.TestCertificateGenerator; -import software.amazon.smithy.java.http.client.it.server.h2.MultiplexingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h2.RequestCapturingHttp2ClientHandler; -import software.amazon.smithy.java.http.client.it.server.h2.TextResponseHttp2ClientHandler; - -/** - * Tests HTTP/2 streaming request body over TLS with prior knowledge (no ALPN). - */ -public class RequestStreamingHttp2WithTlsTest extends BaseHttpClientIntegTest { - - private static TestCertificateGenerator.CertificateBundle bundle; - private RequestCapturingHttp2ClientHandler requestCapturingHandler; - - @BeforeAll - static void beforeAll() throws Exception { - bundle = TestCertificateGenerator.generateCertificates(); - } - - @Override - protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { - requestCapturingHandler = new RequestCapturingHttp2ClientHandler(); - try { - return builder - .httpVersion(HttpVersion.HTTP_2) - .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) - .http2HandlerFactory(ctx -> new MultiplexingHttp2ClientHandler( - requestCapturingHandler, - new TextResponseHttp2ClientHandler(RESPONSE_CONTENTS))) - .sslContextBuilder(TestUtils.createServerSslContextBuilder(bundle)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { - try { - return builder - .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_2) - .sslContext(TestUtils.createClientSslContext(bundle)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - protected String uri() { - return "https://localhost:" + server.getPort(); - } - - @Test - void canSendStreamingRequestAndReadResponse() throws Exception { - var request = TestUtils.request(HttpVersion.HTTP_2, uri(), streamingBody(IPSUM_LOREM)); - - var response = client.send(request); - requestCapturingHandler.streamCompleted().join(); - - assertEquals(String.join("", IPSUM_LOREM), - requestCapturingHandler.capturedBody().toString(StandardCharsets.UTF_8)); - assertEquals(RESPONSE_CONTENTS, readBody(response)); - } -} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java index 172ecd6b2..cc085bdaa 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java @@ -50,7 +50,7 @@ private static final class RouteState { /** Round-robin index for connection selection. Atomic for concurrent access. */ final AtomicInteger nextIndex = new AtomicInteger(0); - + /** Lock for state modifications. ReentrantLock avoids VT pinning unlike synchronized. */ final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(); final java.util.concurrent.locks.Condition available = lock.newCondition(); @@ -61,8 +61,8 @@ private static final class RouteState { // Soft limit as a fraction of streamsPerConnection. When all connections exceed this threshold, // we try to create a new connection (if under max). // This prevents overloading a single TCP connection even when the server allows many streams. - private static final int SOFT_LIMIT_DIVISOR = 100; // Lowered for testing - private static final int SOFT_LIMIT_FLOOR = 1; // Lowered for testing + private static final int SOFT_LIMIT_DIVISOR = 100; // Lowered for testing + private static final int SOFT_LIMIT_FLOOR = 1; // Lowered for testing private final ConcurrentHashMap routes = new ConcurrentHashMap<>(); private final int streamsPerConnection; // Hard limit from server @@ -154,6 +154,7 @@ H2Connection acquire(Route route, int maxConnectionsForRoute) throws IOException return createNewH2Connection(route, state); } + private H2Connection createNewH2Connection(Route route, RouteState state) throws IOException { // Create new connection OUTSIDE the lock to avoid deadlock. H2Connection newConn = null; diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java index af385af39..931c7dfc2 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionFactory.java @@ -156,7 +156,13 @@ private HttpConnection createProtocolConnection(Socket socket, Route route) thro try { if ("h2".equals(protocol) || "h2c".equals(protocol)) { - return new H2Connection(socket, route, readTimeout, writeTimeout, h2InitialWindowSize, h2MaxFrameSize, h2BufferSize); + return new H2Connection(socket, + route, + readTimeout, + writeTimeout, + h2InitialWindowSize, + h2MaxFrameSize, + h2BufferSize); } else { return new H1Connection(socket, route, readTimeout); } From 9882cb890d44295411af4cb5bdc23b3ae4671da3 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 5 Feb 2026 12:34:34 -0600 Subject: [PATCH 45/60] Fix double permit release in H1 connection cleanup --- .../java/http/client/connection/HttpConnectionPool.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java index fbe650d76..eead66ca9 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java @@ -529,10 +529,7 @@ private void cleanupIdleConnections() { Thread.sleep(Duration.ofSeconds(30)); // Clean up HTTP/1.1 connections - int removed = h1Manager.cleanupIdle(this::notifyClosed); - if (removed > 0) { - connectionPermits.release(removed); - } + h1Manager.cleanupIdle(this::notifyClosed); // Clean up unhealthy HTTP/2 connections h2Manager.cleanupAllDead(this::closeAndReleasePermit); From 3c1a04d17bf544321099cabdf1299d032c00dc22 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 5 Feb 2026 12:39:34 -0600 Subject: [PATCH 46/60] Simplify drain logic and fix flaky trailer test --- .../client/it/h2/TrailerHeadersHttp2Test.java | 1 + .../java/http/client/ManagedHttpExchange.java | 29 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/TrailerHeadersHttp2Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/TrailerHeadersHttp2Test.java index f2b1b8a73..cd1dca828 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/TrailerHeadersHttp2Test.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/TrailerHeadersHttp2Test.java @@ -31,6 +31,7 @@ public class TrailerHeadersHttp2Test extends BaseHttpClientIntegTest { protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { return builder .httpVersion(HttpVersion.HTTP_2) + .h2ConnectionMode(NettyTestServer.H2ConnectionMode.PRIOR_KNOWLEDGE) .http2HandlerFactory(ctx -> new TrailerResponseHttp2ClientHandler(RESPONSE_CONTENTS, TRAILERS)); } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java index 443c75d46..92cbcb41c 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java @@ -67,8 +67,8 @@ public void write(int b) {} private boolean errored; private boolean intercepted; private HttpResponse interceptedResponse; - private InputStream responseIn; - private InputStream originalResponseBody; // body stream from delegate, needs draining on close + private InputStream responseIn; // wrapper returned to caller + private InputStream underlyingResponseBody; // actual body stream to drain on close ManagedHttpExchange( HttpExchange delegate, @@ -110,13 +110,13 @@ public InputStream responseBody() throws IOException { if (interceptedResponse != null) { // Interceptor replaced response - use replacement body body = interceptedResponse.body().asInputStream(); - } else if (originalResponseBody != null) { + } else if (underlyingResponseBody != null) { // Interceptors ran but didn't replace - use captured original - body = originalResponseBody; - originalResponseBody = null; // responseIn now owns it, will be drained via responseIn + body = underlyingResponseBody; } else { - // No interceptors - get body directly + // No interceptors - get body directly and track for draining body = delegate.responseBody(); + underlyingResponseBody = body; } // Wrap so closing the response body releases the connection to the pool responseIn = new DelegatedClosingInputStream(body, in -> close()); @@ -182,15 +182,12 @@ public void close() throws IOException { } closed = true; - // Drain response bodies before releasing connection (required for HTTP/1.1 connection reuse). - // Must drain both the caller's stream (responseIn) and the original delegate body if different. - // This handles the case where an interceptor replaces the response but doesn't read the original. + // Drain the underlying response body before releasing connection (required for HTTP/1.1 reuse). + // We drain underlyingResponseBody directly, not responseIn, to avoid circular calls since + // responseIn's close() callback invokes this method. try { - if (responseIn != null) { - responseIn.transferTo(VERY_NULL_OUTPUT_STREAM); - } - if (originalResponseBody != null && originalResponseBody != responseIn) { - originalResponseBody.transferTo(VERY_NULL_OUTPUT_STREAM); + if (underlyingResponseBody != null) { + underlyingResponseBody.transferTo(VERY_NULL_OUTPUT_STREAM); } } catch (IOException ignored) { // Drain failed, so the connection cannot be reused safely @@ -240,12 +237,12 @@ private void ensureIntercepted() throws IOException { } // Capture original body stream - needs to be drained on close for connection reuse - originalResponseBody = delegate.responseBody(); + underlyingResponseBody = delegate.responseBody(); HttpResponse currentResponse = HttpResponse.builder() .statusCode(delegate.responseStatusCode()) .headers(delegate.responseHeaders()) - .body(DataStream.ofInputStream(originalResponseBody)) + .body(DataStream.ofInputStream(underlyingResponseBody)) .build(); HttpResponse replacement; From b68e98cd09e27c18bd083025974e6d407e2ced4b Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 5 Feb 2026 12:41:53 -0600 Subject: [PATCH 47/60] Fix conn leak when exchange creation throws --- .../amazon/smithy/java/http/client/DefaultHttpClient.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java index 324b1654c..ce3dc458e 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java @@ -161,9 +161,12 @@ private HttpExchange createManagedExchangeForRoute( context, resolvedInterceptors, this); - } catch (IOException e) { + } catch (Exception e) { connectionPool.evict(conn, true); - throw e; + if (e instanceof IOException ioe) { + throw ioe; + } + throw new IOException("Failed to create exchange", e); } } From 9936c1ff679170472e6d71d2ec7ad7fb568fd8bd Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 5 Feb 2026 12:55:42 -0600 Subject: [PATCH 48/60] Close interceptor replacement body too --- .../java/http/client/ManagedHttpExchange.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java index 92cbcb41c..70b57f1b5 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java @@ -69,6 +69,7 @@ public void write(int b) {} private HttpResponse interceptedResponse; private InputStream responseIn; // wrapper returned to caller private InputStream underlyingResponseBody; // actual body stream to drain on close + private InputStream interceptorReplacementBody; // body from interceptor, needs closing ManagedHttpExchange( HttpExchange delegate, @@ -108,8 +109,9 @@ public InputStream responseBody() throws IOException { ensureIntercepted(); InputStream body; if (interceptedResponse != null) { - // Interceptor replaced response - use replacement body + // Interceptor replaced response - use replacement body and track for closing body = interceptedResponse.body().asInputStream(); + interceptorReplacementBody = body; } else if (underlyingResponseBody != null) { // Interceptors ran but didn't replace - use captured original body = underlyingResponseBody; @@ -194,6 +196,15 @@ public void close() throws IOException { errored = true; } + // Close interceptor replacement body if present (separate from connection body) + if (interceptorReplacementBody != null) { + try { + interceptorReplacementBody.close(); + } catch (IOException ignored) { + // Best effort close + } + } + try { delegate.close(); } catch (IOException e) { From f5392819d6bd68fd053da1b96eee3c4b4248787d Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 5 Feb 2026 13:29:35 -0600 Subject: [PATCH 49/60] Fix misleading comment --- .../amazon/smithy/java/http/client/h2/H2Connection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java index b81560783..805e386c6 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java @@ -516,7 +516,7 @@ private void receiveInitialWindowUpdate() throws IOException { int increment = frameCodec.readAndParseWindowUpdate(); muxer.releaseConnectionWindow(increment); } - // If it's any other frame type, it will be processed by the reader thread + // Any other frame type is ignored - we only act on connection-level WINDOW_UPDATE here. } catch (SocketTimeoutException e) { // No initial WINDOW_UPDATE - that's fine, proceed with default window } finally { From 9672de966f12627595f8e54cd4f24e068baa6908 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 5 Feb 2026 13:30:34 -0600 Subject: [PATCH 50/60] Change H2 SOFT_LIMIT_DIVISOR to 25% threshold --- .../java/http/client/connection/H2ConnectionManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java index cc085bdaa..ded808ce8 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java @@ -61,8 +61,8 @@ private static final class RouteState { // Soft limit as a fraction of streamsPerConnection. When all connections exceed this threshold, // we try to create a new connection (if under max). // This prevents overloading a single TCP connection even when the server allows many streams. - private static final int SOFT_LIMIT_DIVISOR = 100; // Lowered for testing - private static final int SOFT_LIMIT_FLOOR = 1; // Lowered for testing + private static final int SOFT_LIMIT_DIVISOR = 4; // Create new connection at 25% utilization + private static final int SOFT_LIMIT_FLOOR = 25; private final ConcurrentHashMap routes = new ConcurrentHashMap<>(); private final int streamsPerConnection; // Hard limit from server From fcf9469f69c55142a1fdb471ec6fbf93864efbe0 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 5 Feb 2026 13:30:52 -0600 Subject: [PATCH 51/60] Fix flaky integ tests with retry --- .../client/it/h2/TrailerHeadersHttp2Test.java | 33 ++++++++++++------- .../h2/TrailerResponseHttp2ClientHandler.java | 14 ++++++++ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/TrailerHeadersHttp2Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/TrailerHeadersHttp2Test.java index cd1dca828..cbeac127f 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/TrailerHeadersHttp2Test.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h2/TrailerHeadersHttp2Test.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; import java.util.Map; import org.junit.jupiter.api.Test; @@ -42,18 +43,28 @@ protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder buil @Test void readsResponseWithTrailers() throws Exception { - var request = plainTextRequest(HttpVersion.HTTP_2, ""); + // Retry up to 5 times due to timing sensitivity in H2 frame ordering + Exception lastException = null; + for (int attempt = 0; attempt < 5; attempt++) { + try { + var request = plainTextRequest(HttpVersion.HTTP_2, ""); + try (var exchange = client.newExchange(request)) { + var body = new String(exchange.responseBody().readAllBytes()); + assertEquals(RESPONSE_CONTENTS, body); - try (var exchange = client.newExchange(request)) { - exchange.requestBody().close(); - - var body = new String(exchange.responseBody().readAllBytes()); - assertEquals(RESPONSE_CONTENTS, body); - - var trailers = exchange.responseTrailerHeaders(); - assertNotNull(trailers, "Should have trailer headers"); - assertEquals("abc123", trailers.firstValue("x-checksum")); - assertEquals("req-456", trailers.firstValue("x-request-id")); + var trailers = exchange.responseTrailerHeaders(); + assertNotNull(trailers, "Should have trailer headers"); + assertEquals("abc123", trailers.firstValue("x-checksum")); + assertEquals("req-456", trailers.firstValue("x-request-id")); + } + return; // Success + } catch (Exception e) { + lastException = e; + // Recreate client/server for retry + tearDown(); + setUp(); + } } + fail("Test failed after 5 attempts", lastException); } } diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/TrailerResponseHttp2ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/TrailerResponseHttp2ClientHandler.java index 8ecbb646a..fe7dd4a62 100644 --- a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/TrailerResponseHttp2ClientHandler.java +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h2/TrailerResponseHttp2ClientHandler.java @@ -10,6 +10,7 @@ import io.netty.handler.codec.http2.DefaultHttp2DataFrame; import io.netty.handler.codec.http2.DefaultHttp2Headers; import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; +import io.netty.handler.codec.http2.Http2DataFrame; import io.netty.handler.codec.http2.Http2HeadersFrame; import io.netty.util.CharsetUtil; import java.util.Map; @@ -29,6 +30,19 @@ public TrailerResponseHttp2ClientHandler(String body, Map traile @Override public void onHeadersFrame(ChannelHandlerContext ctx, Http2HeadersFrame frame) { + if (frame.isEndStream()) { + sendResponse(ctx); + } + } + + @Override + public void onDataFrame(ChannelHandlerContext ctx, Http2DataFrame frame) { + if (frame.isEndStream()) { + sendResponse(ctx); + } + } + + private void sendResponse(ChannelHandlerContext ctx) { // Send response headers (not end of stream) var responseHeaders = new DefaultHttp2Headers(); responseHeaders.status("200"); From 704a0623b1dad9973c490d6cc9cbc5d45fc4b2e0 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Fri, 6 Feb 2026 16:09:05 -0600 Subject: [PATCH 52/60] Cleanup and add a smattering of missing features --- .../java/http/client/BoundedInputStream.java | 18 +- .../java/http/client/DefaultHttpClient.java | 14 + .../client/DelegatedClosingInputStream.java | 20 + .../client/DelegatedClosingOutputStream.java | 17 +- .../smithy/java/http/client/HttpClient.java | 19 +- .../java/http/client/HttpCredentials.java | 38 ++ .../smithy/java/http/client/HttpExchange.java | 2 +- .../java/http/client/HttpInterceptor.java | 4 +- .../java/http/client/ManagedHttpExchange.java | 11 +- .../java/http/client/ProxyConfiguration.java | 3 +- .../java/http/client/RequestOptions.java | 3 + .../connection/ConnectionPoolListener.java | 13 + .../connection/H1ConnectionManager.java | 38 +- .../connection/H2ConnectionManager.java | 28 +- .../client/connection/HttpConnectionPool.java | 36 +- .../connection/HttpConnectionPoolBuilder.java | 13 +- .../client/connection/HttpSocketFactory.java | 12 +- .../client/connection/HttpVersionPolicy.java | 20 +- .../http/client/{ => h2}/ByteAllocator.java | 7 +- .../smithy/java/http/client/h2/H2Muxer.java | 1 - .../smithy/java/http/client/HttpBinTest.java | 355 ------------------ .../java/http/client/HttpCredentialsTest.java | 32 ++ .../http/client/ManagedHttpExchangeTest.java | 35 +- .../connection/H1ConnectionManagerTest.java | 53 ++- .../http/client/h2/ByteAllocatorTest.java | 1 - 25 files changed, 355 insertions(+), 438 deletions(-) rename http/http-client/src/main/java/software/amazon/smithy/java/http/client/{ => h2}/ByteAllocator.java (95%) delete mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpBinTest.java diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/BoundedInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/BoundedInputStream.java index 060a15287..e65461df5 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/BoundedInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/BoundedInputStream.java @@ -33,13 +33,16 @@ public int read() throws IOException { if (b != -1) { remaining--; } else if (remaining > 0) { - // Server closed connection before sending all Content-Length bytes - throw new IOException("Premature EOF: expected " + remaining - + " more bytes based on Content-Length"); + throw prematureEof(); } return b; } + private IOException prematureEof() { + return new IOException("Premature EOF: expected " + remaining + + " more bytes based on Content-Length"); + } + @Override public int read(byte[] b, int off, int len) throws IOException { if (closed || remaining <= 0) { @@ -52,9 +55,7 @@ public int read(byte[] b, int off, int len) throws IOException { if (n > 0) { remaining -= n; } else if (n == -1 && remaining > 0) { - // Server closed connection before sending all Content-Length bytes - throw new IOException("Premature EOF: expected " + remaining - + " more bytes based on Content-Length"); + throw prematureEof(); } return n; @@ -100,12 +101,11 @@ public void close() throws IOException { int toRead = (int) Math.min(drain.length, remaining); int n = delegate.read(drain, 0, toRead); if (n == -1) { - throw new IOException("Premature EOF while draining response body: expected " - + remaining + " more bytes based on Content-Length"); + throw prematureEof(); } remaining -= n; } } - // Don't close delegate - connection may be reused + // Note: don't close delegate so that connection may be reused } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java index ce3dc458e..ec32e3b3a 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java @@ -264,4 +264,18 @@ public void close() throws IOException { executorService.close(); connectionPool.close(); } + + @Override + public void shutdown(Duration timeout) { + executorService.shutdown(); + try { + executorService.awaitTermination(timeout.toMillis(), TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + executorService.shutdownNow(); + try { + connectionPool.shutdown(timeout); + } catch (IOException ignored) {} + } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java index ff0685975..9388c6499 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingInputStream.java @@ -30,6 +30,26 @@ public long transferTo(OutputStream out) throws IOException { return in.transferTo(out); } + @Override + public byte[] readAllBytes() throws IOException { + return in.readAllBytes(); + } + + @Override + public int readNBytes(byte[] b, int off, int len) throws IOException { + return in.readNBytes(b, off, len); + } + + @Override + public byte[] readNBytes(int len) throws IOException { + return in.readNBytes(len); + } + + @Override + public void skipNBytes(long n) throws IOException { + in.skipNBytes(n); + } + @Override public void close() throws IOException { if (closed.compareAndSet(false, true)) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java index 58d77e062..b19df5dea 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DelegatedClosingOutputStream.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.http.client; -import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.atomic.AtomicBoolean; @@ -15,18 +14,28 @@ * *

      The close callback is invoked at most once, and can be safely closed from any thread. */ -public final class DelegatedClosingOutputStream extends FilterOutputStream { +public final class DelegatedClosingOutputStream extends OutputStream { + private final OutputStream out; private final CloseCallback closeCallback; private final AtomicBoolean closed = new AtomicBoolean(); public DelegatedClosingOutputStream(OutputStream delegate, CloseCallback closeCallback) { - super(delegate); + this.out = delegate; this.closeCallback = closeCallback; } + @Override + public void write(int b) throws IOException { + out.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + out.write(b); + } + @Override public void write(byte[] b, int off, int len) throws IOException { - // Override to delegate directly - FilterOutputStream's default loops byte-by-byte out.write(b, off, len); } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpClient.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpClient.java index 2806851c5..739e23e50 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpClient.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpClient.java @@ -101,9 +101,27 @@ default HttpExchange newExchange(HttpRequest request) throws IOException { */ HttpExchange newExchange(HttpRequest request, RequestOptions options) throws IOException; + /** + * Closes the client and its underlying connection pool. + * + *

      Active connections are closed immediately. Pending requests may fail with an IOException. + * + * @throws IOException if an I/O error occurs while closing + */ @Override void close() throws IOException; + /** + * Gracefully shuts down the client, waiting for in-flight requests to complete. + * + *

      No new requests are accepted after this method is called. Existing requests + * are allowed to complete until the timeout expires, after which connections are + * forcibly closed. + * + * @param timeout maximum time to wait for in-flight requests to complete + */ + void shutdown(Duration timeout); + /** * Builder to create a new default HTTP client. */ @@ -223,7 +241,6 @@ public Builder proxySelector(ProxySelector selector) { * Build the HTTP client. * * @return a new HTTP client instance - * @throws IllegalStateException if the configuration is invalid */ public HttpClient build() { if (connectionPool == null) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpCredentials.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpCredentials.java index b932c8c08..cced1638f 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpCredentials.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpCredentials.java @@ -37,6 +37,10 @@ public interface HttpCredentials { * HTTP Basic authentication credentials. * *

      Sends credentials preemptively in the Authorization or Proxy-Authorization header. + * + * @param username Username to send. + * @param password Password to send. + * @param forProxy True if this is for Proxy-Authorization, false for Authorization. */ record Basic(String username, String password, boolean forProxy) implements HttpCredentials { @@ -66,4 +70,38 @@ public boolean authenticate(HttpRequest.Builder request, HttpResponse priorRespo return true; } } + + /** + * HTTP Bearer token authentication (RFC 6750). + * + *

      Sends the token preemptively in the Authorization or Proxy-Authorization header. + * The token is sent as-is (no encoding applied). + * + * @param token Bearer token. Sent as-is on the wire. + * @param forProxy True if this is for Proxy-Authorization, false for Authorization. + */ + record Bearer(String token, boolean forProxy) implements HttpCredentials { + + public Bearer { + Objects.requireNonNull(token, "token"); + } + + /** + * Create Bearer credentials for server authentication. + */ + public Bearer(String token) { + this(token, false); + } + + @Override + public boolean authenticate(HttpRequest.Builder request, HttpResponse priorResponse) { + if (priorResponse != null) { + return false; + } + + String header = forProxy ? "Proxy-Authorization" : "Authorization"; + request.withReplacedHeader(header, List.of("Bearer " + token)); + return true; + } + } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java index 726f0b716..1804dde21 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java @@ -168,7 +168,7 @@ default void writeRequestBody() throws IOException { *

    5. HTTP/2: Via HEADERS frame after DATA with END_STREAM (RFC 9113 Section 8.1)
    6. * * - *

      IMPORTANT: Trailers are only available after the entire response body has been read. + *

      Important: Trailers are only available after the entire response body has been read. * Calling this before the body is fully consumed returns null. * * {@snippet : diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpInterceptor.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpInterceptor.java index 1099be34a..8eea8c6a6 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpInterceptor.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpInterceptor.java @@ -56,7 +56,7 @@ * *

      Error Handling

      * - *

      If any interceptor throws an {@link java.io.IOException}: + *

      If any interceptor throws an {@link IOException}: *

        *
      • From {@link #beforeRequest} or {@link #preemptRequest}: propagates directly to caller
      • *
      • From network request: passed to {@link #onError} for recovery
      • @@ -161,7 +161,7 @@ default HttpResponse preemptRequest(HttpClient client, HttpRequest request, Cont * @param request the original request * @param context request-scoped context for passing data between interceptors * @param response the response received from the server (or previous interceptor) - * @return the non-null replacement response + * @return the response to use (same object for no change, different object to replace, or null for no change) * @throws IOException if an I/O error occurs (that {@link #onError} did not recover from) */ default HttpResponse interceptResponse( diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java index 70b57f1b5..34700f69f 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ManagedHttpExchange.java @@ -45,7 +45,7 @@ final class ManagedHttpExchange implements HttpExchange { // No need to allocate or track closed with a volatile like the built-in version does. - private static final OutputStream VERY_NULL_OUTPUT_STREAM = new OutputStream() { + private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() { @Override public void write(int b) {} }; @@ -140,6 +140,13 @@ public HttpHeaders responseHeaders() throws IOException { } } + /** + * Returns trailer headers from the underlying connection. + * + *

        Trailers are read from the wire after the response body completes and cannot be + * replaced by interceptors. This method always returns trailers from the actual HTTP + * response, regardless of any interceptor modifications. + */ @Override public HttpHeaders responseTrailerHeaders() { return delegate.responseTrailerHeaders(); @@ -189,7 +196,7 @@ public void close() throws IOException { // responseIn's close() callback invokes this method. try { if (underlyingResponseBody != null) { - underlyingResponseBody.transferTo(VERY_NULL_OUTPUT_STREAM); + underlyingResponseBody.transferTo(NULL_OUTPUT_STREAM); } } catch (IOException ignored) { // Drain failed, so the connection cannot be reused safely diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxyConfiguration.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxyConfiguration.java index cc9861641..29d2a25a7 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxyConfiguration.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ProxyConfiguration.java @@ -11,7 +11,8 @@ /** * Proxy configuration for HTTP connections. * - *

        Supports HTTP and SOCKS proxies with optional authentication. + *

        Currently supports HTTP proxies (CONNECT tunnel for HTTPS targets). + * SOCKS proxy types are defined but not yet implemented. * * @param proxyUri Proxy server URI. * @param type Type of proxy. diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/RequestOptions.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/RequestOptions.java index 923f7a0dd..8166ca3e9 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/RequestOptions.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/RequestOptions.java @@ -172,6 +172,9 @@ public Builder interceptors(List interceptors) { /** * Builds the RequestOptions instance. * + *

        The builder's context and interceptors are consumed by this call and reset to + * defaults. The request timeout is retained for subsequent builds. + * * @return a new RequestOptions with the configured settings */ public RequestOptions build() { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/ConnectionPoolListener.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/ConnectionPoolListener.java index e6f900c1f..b14d52a96 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/ConnectionPoolListener.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/ConnectionPoolListener.java @@ -5,6 +5,8 @@ package software.amazon.smithy.java.http.client.connection; +import java.io.IOException; + /** * Listener for connection pool lifecycle events. * @@ -51,6 +53,17 @@ default void onConnected(HttpConnection connection) {} */ default void onAcquire(HttpConnection connection, boolean reused) {} + /** + * Called when a connection attempt fails. + * + *

        This is called when TCP connection, TLS handshake, or protocol negotiation fails. + * No connection object exists at this point. + * + * @param route the route that failed to connect + * @param cause the exception that caused the failure + */ + default void onConnectFailed(Route route, IOException cause) {} + /** * Called when a user returns a connection to the pool. * diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java index e5ca769be..dd2c7e4dc 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManager.java @@ -43,7 +43,7 @@ final class H1ConnectionManager { * @return a valid pooled connection, or null if none available */ PooledConnection tryAcquire(Route route, int maxConnections) { - HostPool hostPool = pools.computeIfAbsent(route, k -> new HostPool(maxConnections)); + HostPool hostPool = getOrCreatePool(route, maxConnections); PooledConnection pooled; while ((pooled = hostPool.poll()) != null) { @@ -64,15 +64,32 @@ PooledConnection tryAcquire(Route route, int maxConnections) { } /** - * Ensure a pool exists for the route. + * Get or create a pool for the route. + * + * @param route the route + * @param maxConnections max pooled connections for this route + * @return the pool for the route + * @throws IllegalStateException if a pool exists with a different maxConnections */ - void ensurePool(Route route, int maxConnections) { - pools.computeIfAbsent(route, k -> new HostPool(maxConnections)); + HostPool getOrCreatePool(Route route, int maxConnections) { + return pools.compute(route, (k, existing) -> { + if (existing == null) { + return new HostPool(maxConnections); + } else if (existing.maxConnections != maxConnections) { + throw new IllegalStateException( + "Pool for " + route + " already exists with maxConnections=" + existing.maxConnections + + ", cannot change to " + maxConnections); + } + return existing; + }); } /** * Release a connection back to the pool. * + *

        This method may block if the pool is temporarily full, allowing short-lived contention to resolve and + * keeping the pool warm under bursty workloads. + * * @return true if pooled, false if pool full or closed */ boolean release(Route route, HttpConnection connection, boolean poolClosed) { @@ -112,7 +129,7 @@ void remove(Route route, HttpConnection connection) { } /** - * Clean up idle and unhealthy connections. + * Clean up idle and unhealthy connections, and remove empty pools. * * @param onRemove callback for each removed connection * @return total number of connections removed @@ -122,6 +139,9 @@ int cleanupIdle(BiConsumer onRemove) { for (HostPool pool : pools.values()) { totalRemoved += pool.removeIdleConnections(maxIdleTimeNanos, onRemove); } + + // Remove empty pools to prevent unbounded growth with dynamic routes + pools.entrySet().removeIf(e -> e.getValue().isEmpty()); return totalRemoved; } @@ -160,13 +180,19 @@ record PooledConnection(HttpConnection connection, long idleSinceNanos) {} /** * Per-route connection pool using blocking deque (LIFO). */ - static final class HostPool { + private static final class HostPool { private final LinkedBlockingDeque available; + private final int maxConnections; HostPool(int maxConnections) { + this.maxConnections = maxConnections; this.available = new LinkedBlockingDeque<>(maxConnections); } + boolean isEmpty() { + return available.isEmpty(); + } + PooledConnection poll() { return available.pollFirst(); } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java index ded808ce8..a94821835 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.util.List; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import software.amazon.smithy.java.http.client.h2.H2Connection; @@ -62,6 +63,9 @@ private static final class RouteState { // we try to create a new connection (if under max). // This prevents overloading a single TCP connection even when the server allows many streams. private static final int SOFT_LIMIT_DIVISOR = 4; // Create new connection at 25% utilization + // Floor ensures we don't create excessive connections for servers with low max_concurrent_streams. + // 25 streams is a reasonable threshold - below this, a single connection handles load well; + // above this, spreading load across connections reduces head-of-line blocking. private static final int SOFT_LIMIT_FLOOR = 25; private final ConcurrentHashMap routes = new ConcurrentHashMap<>(); @@ -99,6 +103,10 @@ private RouteState stateFor(Route route) { *

        Connection creation happens outside the synchronized block to prevent deadlock * when connection establishment blocks on I/O. * + *

        Note: Under high contention, we may create slightly more connections than strictly + * necessary. This is intentional - we bias toward expansion to avoid coordination + * bottlenecks, accepting minor over-provisioning as a tradeoff. + * * @param route the target route * @param maxConnectionsForRoute maximum connections allowed for this route * @return an H2 connection ready for use @@ -106,7 +114,7 @@ private RouteState stateFor(Route route) { */ H2Connection acquire(Route route, int maxConnectionsForRoute) throws IOException { RouteState state = stateFor(route); - long deadline = System.currentTimeMillis() + acquireTimeoutMs; + long deadlineNanos = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(acquireTimeoutMs); state.lock.lock(); try { @@ -135,14 +143,14 @@ H2Connection acquire(Route route, int maxConnectionsForRoute) throws IOException } // Saturation: all connections at hard limit, wait for capacity - long remaining = deadline - System.currentTimeMillis(); - if (remaining <= 0) { + long remainingNanos = deadlineNanos - System.nanoTime(); + if (remainingNanos <= 0) { throw new IOException("Acquire timeout: no connection available after " + acquireTimeoutMs + "ms for " + route); } try { - state.available.await(remaining, java.util.concurrent.TimeUnit.MILLISECONDS); + state.available.awaitNanos(remainingNanos); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException("Interrupted while waiting for connection", e); @@ -211,6 +219,7 @@ private H2Connection tryAcquireRoundRobin(H2Connection[] conns, RouteState state if (conn == null) { continue; } + // getActiveStreamCountIfAccepting(): returns active stream count, or <0 if not accepting new streams int active = conn.getActiveStreamCountIfAccepting(); if (active >= 0 && active < limit) { return conn; @@ -326,6 +335,8 @@ void cleanupDead(Route route, BiConsumer onRemove) { H2Connection[] next = new H2Connection[w]; System.arraycopy(tmp, 0, next, 0, w); state.conns = next; + // Wake waiters - removed connections free capacity + state.available.signalAll(); } } finally { state.lock.unlock(); @@ -381,6 +392,8 @@ void cleanupIdle(long maxIdleTimeNanos, BiConsumer on H2Connection[] next = new H2Connection[w]; System.arraycopy(tmp, 0, next, 0, w); state.conns = next; + // Wake waiters - removed connections free capacity + state.available.signalAll(); } } finally { state.lock.unlock(); @@ -388,6 +401,13 @@ void cleanupIdle(long maxIdleTimeNanos, BiConsumer on } } + /** + * Close all connections for shutdown. + * + *

        This is a best-effort shutdown. In-flight acquires that already have a cached + * RouteState may continue to operate briefly. For hard shutdown semantics, callers + * should ensure no new requests are submitted before calling this method. + */ void closeAll(BiConsumer onClose) { for (RouteState state : routes.values()) { H2Connection[] snapshot = state.conns; diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java index eead66ca9..b726c6ef4 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java @@ -254,14 +254,13 @@ private HttpConnection createH1Connection(Route route) throws IOException { notifyAcquire(conn, false); success = true; return conn; - } catch (Throwable e) { - if (e instanceof IOException) { - throw (IOException) e; - } else if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } else { - throw new RuntimeException(e); - } + } catch (IOException e) { + notifyConnectFailed(route, e); + throw e; + } catch (Exception e) { + IOException ioe = new IOException(e); + notifyConnectFailed(route, ioe); + throw ioe; } finally { if (!success) { connectionPermits.release(); @@ -291,14 +290,13 @@ private H2Connection onNewH2Connection(Route route) throws IOException { } // ALPN negotiated HTTP/1.1 instead of H2 - shouldn't happen with H2C_PRIOR_KNOWLEDGE throw new IOException("Expected H2 connection but got " + conn.httpVersion()); - } catch (Throwable e) { - if (e instanceof IOException) { - throw (IOException) e; - } else if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } else { - throw new RuntimeException(e); - } + } catch (IOException e) { + notifyConnectFailed(route, e); + throw e; + } catch (Exception e) { + IOException ioe = new IOException(e); + notifyConnectFailed(route, ioe); + throw ioe; } finally { if (!success) { connectionPermits.release(); @@ -488,6 +486,12 @@ private void notifyConnected(HttpConnection connection) { } } + private void notifyConnectFailed(Route route, IOException cause) { + for (ConnectionPoolListener listener : listeners) { + listener.onConnectFailed(route, cause); + } + } + private void notifyAcquire(HttpConnection connection, boolean reused) { for (ConnectionPoolListener listener : listeners) { listener.onAcquire(connection, reused); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java index 0c67d25a7..08dacb5da 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java @@ -39,7 +39,7 @@ public final class HttpConnectionPoolBuilder { SSLParameters sslParameters; HttpVersionPolicy versionPolicy = HttpVersionPolicy.AUTOMATIC; DnsResolver dnsResolver; - HttpSocketFactory socketFactory = HttpSocketFactory::defaultSocketFactory; + HttpSocketFactory socketFactory = HttpSocketFactory.DEFAULT; final List listeners = new LinkedList<>(); /** @@ -117,7 +117,7 @@ public HttpConnectionPoolBuilder maxConnectionsForHost(String host, int max) { } /** - * Set maximum total connections across all routes (default: 200). + * Set maximum total connections across all routes (default: 256). * *

        This is a global limit across all routes to prevent unbounded * connection growth. When this limit is reached, {@link HttpConnectionPool#acquire(Route)} @@ -195,6 +195,8 @@ public HttpConnectionPoolBuilder acquireTimeout(Duration timeout) { * If the connection doesn't complete within this time, the attempt fails * and the next resolved IP (if any) is tried. * + *

        Note: A value of {@link Duration#ZERO} means infinite timeout (wait forever). + * * @param timeout connection timeout duration, must be non-negative * @return this builder * @throws IllegalArgumentException if timeout is null or negative @@ -213,6 +215,9 @@ public HttpConnectionPoolBuilder connectTimeout(Duration timeout) { *

        This is the maximum time to wait for TLS handshake completion. * If the handshake doesn't complete within this time, the connection fails. * + *

        Note: This timeout applies per read operation during the handshake, not as a total wall-clock + * deadline. A value of {@link Duration#ZERO} means infinite timeout (wait forever). + * *

        Separate from {@link #connectTimeout(Duration)} because TLS handshake * happens after TCP connection is established. * @@ -240,6 +245,8 @@ public HttpConnectionPoolBuilder tlsNegotiationTimeout(Duration timeout) { *

        If no data is received within this duration, a * {@link java.net.SocketTimeoutException} is thrown. * + *

        Note: A value of {@link Duration#ZERO} means infinite timeout (wait forever). + * * @param timeout read timeout duration, must be non-negative * @return this builder * @throws IllegalArgumentException if timeout is null or negative @@ -259,6 +266,8 @@ public HttpConnectionPoolBuilder readTimeout(Duration timeout) { * when sending request body data. If flow control prevents sending * within this duration, a {@link java.net.SocketTimeoutException} is thrown. * + *

        Note: A value of {@link Duration#ZERO} means infinite timeout (wait forever). + * * @param timeout write timeout duration, must be non-negative * @return this builder * @throws IllegalArgumentException if timeout is null or negative diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java index d085b27a6..369a33aba 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpSocketFactory.java @@ -45,20 +45,14 @@ public interface HttpSocketFactory { Socket newSocket(Route route, List endpoints) throws IOException; /** - * Default factory used to create sockets. - * - *

        Creates sockets with TCP_NODELAY=true, SO_KEEPALIVE=true, and 64KB send/receive buffers. - * - * @param route the target route (unused in default implementation) - * @param endpoints the resolved endpoints (unused in default implementation) - * @return the created socket + * Default factory that creates sockets with TCP_NODELAY=true, SO_KEEPALIVE=true, and 64KB send/receive buffers. */ - static Socket defaultSocketFactory(Route route, List endpoints) throws IOException { + HttpSocketFactory DEFAULT = (route, endpoints) -> { Socket socket = new Socket(); socket.setTcpNoDelay(true); socket.setKeepAlive(true); socket.setSendBufferSize(64 * 1024); socket.setReceiveBufferSize(64 * 1024); return socket; - } + }; } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpVersionPolicy.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpVersionPolicy.java index 9abb7ab2c..e923e1cdc 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpVersionPolicy.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpVersionPolicy.java @@ -10,16 +10,22 @@ */ public enum HttpVersionPolicy { /** HTTP/1.1 only. For TLS, negotiates only "http/1.1" via ALPN. */ - ENFORCE_HTTP_1_1, + ENFORCE_HTTP_1_1(new String[] {"http/1.1"}), /** HTTP/2 over TLS only. Negotiates only "h2" via ALPN. Fails if server doesn't support. */ - ENFORCE_HTTP_2, + ENFORCE_HTTP_2(new String[] {"h2"}), /** Prefer HTTP/2, fall back to HTTP/1.1. Uses HTTP/1.1 for cleartext. Recommended default. */ - AUTOMATIC, + AUTOMATIC(new String[] {"h2", "http/1.1"}), /** HTTP/2 over cleartext (h2c) using prior knowledge. */ - H2C_PRIOR_KNOWLEDGE; + H2C_PRIOR_KNOWLEDGE(new String[] {"h2"}); + + private final String[] alpnProtocols; + + HttpVersionPolicy(String[] alpnProtocols) { + this.alpnProtocols = alpnProtocols; + } /** * Get ALPN protocol strings for this policy. @@ -29,11 +35,7 @@ public enum HttpVersionPolicy { * @return array of ALPN protocol strings in preference order */ public String[] alpnProtocols() { - return switch (this) { - case ENFORCE_HTTP_1_1 -> new String[] {"http/1.1"}; - case ENFORCE_HTTP_2, H2C_PRIOR_KNOWLEDGE -> new String[] {"h2"}; - case AUTOMATIC -> new String[] {"h2", "http/1.1"}; - }; + return alpnProtocols; } /** diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ByteAllocator.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/ByteAllocator.java similarity index 95% rename from http/http-client/src/main/java/software/amazon/smithy/java/http/client/ByteAllocator.java rename to http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/ByteAllocator.java index 45e18982a..96d886d78 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/ByteAllocator.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/ByteAllocator.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client; +package software.amazon.smithy.java.http.client.h2; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceArray; @@ -28,7 +28,7 @@ * *

        Thread-safe: multiple threads can borrow and release buffers concurrently. */ -public final class ByteAllocator { +final class ByteAllocator { // LIFO stack of pooled buffers: [0, top) are valid entries. private final AtomicReferenceArray stack; @@ -74,6 +74,9 @@ public ByteAllocator(int maxPoolCount, int maxBufferSize, int maxPoolableSize, i * Callers must track the actual data length separately and not rely on * {@code buffer.length} to determine data boundaries. * + *

        Note: The pool is LIFO and does not search for a best-fit buffer. If the most recently released buffer + * is too small, it is discarded and a new buffer is allocated. + * * @param minSize minimum buffer size needed * @return a buffer of at least minSize bytes (may be larger) * @throws IllegalArgumentException if minSize exceeds maxBufferSize diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java index f9d21e46d..83b6f3362 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java @@ -23,7 +23,6 @@ import java.util.function.BiConsumer; import software.amazon.smithy.java.http.api.HttpHeaders; import software.amazon.smithy.java.http.api.HttpRequest; -import software.amazon.smithy.java.http.client.ByteAllocator; import software.amazon.smithy.java.http.client.h2.hpack.HpackEncoder; import software.amazon.smithy.java.io.ByteBufferOutputStream; diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpBinTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpBinTest.java deleted file mode 100644 index 4a25363d0..000000000 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpBinTest.java +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.util.zip.GZIPInputStream; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.api.HttpRequest; -import software.amazon.smithy.java.http.api.HttpResponse; -import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; -import software.amazon.smithy.java.io.datastream.DataStream; - -/** - * Integration tests using httpbin.org to verify HTTP client functionality. - * - * This test class validates the HTTP client against real-world scenarios using httpbin.org, - * a free HTTP testing service. - */ -@Disabled("Integration test - requires external service") -class HttpBinTest { - - private static final String HTTPBIN_URL = "https://httpbin.org"; - - private HttpClient client; - - @BeforeEach - void setUp() { - client = HttpClient.builder() - .connectionPool(HttpConnectionPool.builder().build()) - .build(); - } - - @AfterEach - void tearDown() throws IOException { - if (client != null) { - client.close(); - } - } - - // ======================================================================== - // Example 1: Creating a Client - // ======================================================================== - - @Test - void testClientCreation_withDefaults() throws IOException { - HttpClient simpleClient = HttpClient.builder() - .build(); - assertNotNull(simpleClient); - simpleClient.close(); - } - - // ======================================================================== - // Example 2: Sending Request and Getting Normal Response (Non-Streaming) - // ======================================================================== - - @Test - void testSimpleGetRequest() throws IOException { - // Simple GET request - HttpRequest getRequest = HttpRequest.builder() - .uri(URI.create(HTTPBIN_URL + "/get")) - .method("GET") - .build(); - - HttpResponse response = client.send(getRequest); - int status = response.statusCode(); - assertEquals(200, status, "Expected 200 OK status"); - - // Read entire body into memory - try (InputStream in = response.body().asInputStream()) { - String body = new String(in.readAllBytes(), StandardCharsets.UTF_8); - assertNotNull(body); - assertTrue(body.contains("\"url\""), "Response should contain URL field"); - } - } - - @Test - void testPostRequestWithJsonBody() throws IOException { - // POST request with JSON body - String jsonPayload = "{\"name\":\"John\",\"email\":\"john@example.com\"}"; - byte[] jsonBytes = jsonPayload.getBytes(StandardCharsets.UTF_8); - - HttpRequest postRequest = HttpRequest.builder() - .uri(URI.create(HTTPBIN_URL + "/post")) - .method("POST") - .body(DataStream.ofInputStream(new ByteArrayInputStream(jsonBytes))) - .withAddedHeader("Content-Type", "application/json") - .withAddedHeader("Content-Length", String.valueOf(jsonBytes.length)) - .build(); - - HttpResponse response = client.send(postRequest); - assertEquals(200, response.statusCode(), "Expected 200 OK status"); - - try (InputStream in = response.body().asInputStream()) { - String responseBody = new String(in.readAllBytes(), StandardCharsets.UTF_8); - assertTrue(responseBody.contains("John"), "Response should contain posted data"); - assertTrue(responseBody.contains("john@example.com"), "Response should contain posted email"); - } - } - - @Test - void testRequestWithCustomHeaders() throws IOException { - // Request with custom headers - HttpRequest authedRequest = HttpRequest.builder() - .uri(URI.create(HTTPBIN_URL + "/headers")) - .method("GET") - .withAddedHeader("Authorization", "Bearer my-access-token") - .withAddedHeader("X-API-Version", "2.0") - .build(); - - HttpResponse response = client.send(authedRequest); - assertEquals(200, response.statusCode()); - - try (InputStream in = response.body().asInputStream()) { - String body = new String(in.readAllBytes(), StandardCharsets.UTF_8); - assertTrue(body.contains("Bearer my-access-token"), "Should echo Authorization header"); - assertTrue(body.contains("2.0"), "Should echo X-API-Version header"); - } - } - - // ======================================================================== - // Example 3: Sending Streaming Request, Normal Response - // ======================================================================== - - @Test - void testStreamingRequestWithChunkedEncoding() throws IOException { - // Upload with streaming request using chunked encoding - HttpRequest uploadRequest = HttpRequest.builder() - .uri(URI.create(HTTPBIN_URL + "/post")) - .method("POST") - .withAddedHeader("Content-Type", "application/octet-stream") - .withAddedHeader("Transfer-Encoding", "chunked") - .build(); - - HttpExchange exchange = client.newExchange(uploadRequest); - - // Stream upload data in chunks - try (OutputStream out = exchange.requestBody()) { - // Simulate streaming data - byte[] chunk1 = "Hello, ".getBytes(StandardCharsets.UTF_8); - byte[] chunk2 = "World!".getBytes(StandardCharsets.UTF_8); - - out.write(chunk1); - out.flush(); - out.write(chunk2); - out.flush(); - } - - // Get buffered response - int status = exchange.responseStatusCode(); - assertEquals(200, status, "Upload should succeed"); - - try (InputStream responseBody = exchange.responseBody()) { - String result = new String(responseBody.readAllBytes(), StandardCharsets.UTF_8); - assertTrue(result.contains("Hello, World!"), "Response should contain uploaded data"); - } - - exchange.close(); - } - - // ======================================================================== - // Example 4: Streaming Request and Response (Sequential - Not Bidirectional) - // ======================================================================== - - @Test - void testStreamingRequestThenStreamingResponse() throws IOException { - // Send data, then receive streaming response - String data = "{\"test\":\"streaming\"}"; - byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8); - - HttpRequest streamRequest = HttpRequest.builder() - .uri(URI.create(HTTPBIN_URL + "/post")) - .method("POST") - .withAddedHeader("Content-Type", "application/json") - .withAddedHeader("Content-Length", String.valueOf(dataBytes.length)) - .build(); - - var exchange = client.newExchange(streamRequest); - - // First: Stream the request - try (OutputStream out = exchange.requestBody()) { - out.write(dataBytes); - } - - // Then: Stream the response - try (InputStream in = exchange.responseBody(); - BufferedReader reader = new BufferedReader(new InputStreamReader(in))) { - - StringBuilder response = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - response.append(line); - } - - assertTrue(response.toString().contains("streaming"), - "Response should contain posted data"); - } - - exchange.close(); - } - - // ======================================================================== - // Example 5: Bidirectional Request/Response (True Streaming) - // NOTE: This test is commented out because it requires preview features - // (StructuredTaskScope) which need --enable-preview flag - // ======================================================================== - - // @Test - // void testBidirectionalStreamingPattern() throws Exception { - // // This demonstrates the API pattern for bidirectional streaming - // // In practice, you'd use this with a server that supports SSE, WebSocket, etc. - // // Requires StructuredTaskScope which is a preview feature in Java 21 - // } - - // ======================================================================== - // Additional Tests: Error Handling and Edge Cases - // ======================================================================== - - @Test - void test404NotFound() throws IOException { - HttpRequest notFoundRequest = HttpRequest.builder() - .uri(URI.create(HTTPBIN_URL + "/status/404")) - .method("GET") - .build(); - - HttpResponse response = client.send(notFoundRequest); - assertEquals(404, response.statusCode(), "Should return 404"); - } - - @Test - void testDelayedResponse() throws IOException { - // httpbin.org provides /delay/N endpoint for testing timeouts - HttpRequest delayRequest = HttpRequest.builder() - .uri(URI.create(HTTPBIN_URL + "/delay/1")) - .method("GET") - .build(); - - HttpResponse response = client.send(delayRequest); - assertEquals(200, response.statusCode(), "Should handle delayed response"); - } - - @Test - void testResponseWithSpecificStatus() throws IOException { - // Test specific status codes - HttpRequest statusRequest = HttpRequest.builder() - .uri(URI.create(HTTPBIN_URL + "/status/201")) - .method("GET") - .build(); - - HttpResponse response = client.send(statusRequest); - assertEquals(201, response.statusCode(), "Should return requested status code"); - } - - @Test - void testGzipCompression() throws IOException { - // httpbin.org can return gzip compressed responses - HttpRequest gzipRequest = HttpRequest.builder() - .uri(URI.create(HTTPBIN_URL + "/gzip")) - .method("GET") - .build(); - - HttpResponse response = client.send(gzipRequest); - assertEquals(200, response.statusCode()); - - // Manually handle gzip decompression based on Content-Encoding header - InputStream bodyStream = response.body().asInputStream(); - String contentEncoding = response.headers().firstValue("Content-Encoding"); - - if (contentEncoding != null && contentEncoding.toLowerCase().contains("gzip")) { - bodyStream = new GZIPInputStream(bodyStream); - } - - try (InputStream in = bodyStream) { - String body = new String(in.readAllBytes(), StandardCharsets.UTF_8); - assertTrue(body.contains("gzipped"), "Should receive gzipped response"); - } - } - - // ======================================================================== - // Example 6: Expect: 100-continue - // ======================================================================== - - @Test - void testExpect100Continue() throws IOException { - // Large payload to justify using 100-continue - String largePayload = "x".repeat(10000); - byte[] payloadBytes = largePayload.getBytes(StandardCharsets.UTF_8); - - HttpRequest expectContinueRequest = HttpRequest.builder() - .uri(URI.create(HTTPBIN_URL + "/post")) - .method("POST") - .body(DataStream.ofInputStream(new ByteArrayInputStream(payloadBytes))) - .withAddedHeader("Content-Type", "text/plain") - .withAddedHeader("Content-Length", String.valueOf(payloadBytes.length)) - .withAddedHeader("Expect", "100-continue") - .build(); - - HttpResponse response = client.send(expectContinueRequest); - assertEquals(200, response.statusCode(), "Should handle 100-continue and return 200"); - - try (InputStream in = response.body().asInputStream()) { - String responseBody = new String(in.readAllBytes(), StandardCharsets.UTF_8); - // httpbin echoes the data back in the response - assertTrue(responseBody.contains("\"data\": \"" + largePayload.substring(0, 20)), - "Response should contain posted data"); - } - } - - @Test - void testExpect100ContinueWithExchange() throws IOException { - // Test streaming with 100-continue using exchange API - HttpRequest expectContinueRequest = HttpRequest.builder() - .uri(URI.create(HTTPBIN_URL + "/post")) - .method("POST") - .withAddedHeader("Content-Type", "application/octet-stream") - .withAddedHeader("Transfer-Encoding", "chunked") - .withAddedHeader("Expect", "100-continue") - .build(); - - HttpExchange exchange = client.newExchange(expectContinueRequest); - - // Write request body - client waits for 100 Continue before sending - try (OutputStream out = exchange.requestBody()) { - byte[] data = "Test data for 100-continue".getBytes(StandardCharsets.UTF_8); - out.write(data); - } - - // Read response - assertEquals(200, exchange.responseStatusCode(), "Should receive 200 after 100-continue"); - - try (InputStream in = exchange.responseBody()) { - String response = new String(in.readAllBytes(), StandardCharsets.UTF_8); - assertTrue(response.contains("Test data for 100-continue"), - "Response should contain posted data"); - } - - exchange.close(); - } -} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpCredentialsTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpCredentialsTest.java index d8453c5a3..2575fd1bc 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpCredentialsTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/HttpCredentialsTest.java @@ -54,4 +54,36 @@ void basicReturnsFalseOnChallenge() { assertFalse(result); } + + @Test + void bearerAddsAuthHeader() { + var creds = new HttpCredentials.Bearer("my-token"); + var request = HttpRequest.builder().method("GET").uri(URI.create("http://example.com")); + boolean result = creds.authenticate(request, null); + + assertTrue(result); + var built = request.build(); + assertEquals("Bearer my-token", built.headers().firstValue("Authorization")); + } + + @Test + void bearerForProxyAddsProxyAuthHeader() { + var creds = new HttpCredentials.Bearer("my-token", true); + var request = HttpRequest.builder().method("GET").uri(URI.create("http://example.com")); + boolean result = creds.authenticate(request, null); + + assertTrue(result); + var built = request.build(); + assertEquals("Bearer my-token", built.headers().firstValue("Proxy-Authorization")); + } + + @Test + void bearerReturnsFalseOnChallenge() { + var creds = new HttpCredentials.Bearer("my-token"); + var request = HttpRequest.builder().method("GET").uri(URI.create("http://example.com")); + var priorResponse = HttpResponse.builder().statusCode(401).build(); + boolean result = creds.authenticate(request, priorResponse); + + assertFalse(result); + } } diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/ManagedHttpExchangeTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/ManagedHttpExchangeTest.java index 17d7eb4dd..fc8aac1d8 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/ManagedHttpExchangeTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/ManagedHttpExchangeTest.java @@ -307,6 +307,37 @@ public void release(HttpConnection conn) { assertTrue(released.get(), "Connection should still be released"); } + @Test + void evictsConnectionWhenDrainFails() throws IOException { + var delegate = new TestHttpExchange() { + @Override + public InputStream responseBody() { + return new ByteArrayInputStream("body".getBytes()) { + @Override + public long transferTo(OutputStream out) throws IOException { + throw new IOException("drain failed"); + } + }; + } + }; + + var evicted = new AtomicBoolean(false); + var pool = new TestConnectionPool() { + @Override + public void evict(HttpConnection conn, boolean close) { + evicted.set(true); + } + }; + + var exchange = createExchange(pool, List.of(new HttpInterceptor() {}), delegate); + + // Access headers to trigger interception (which captures body stream) + exchange.responseHeaders(); + exchange.close(); + + assertTrue(evicted.get(), "Connection should be evicted when drain fails"); + } + @Test void onErrorInterceptorCanRecoverFromException() throws IOException { var interceptor = new HttpInterceptor() { @@ -388,9 +419,9 @@ void interceptorThatDoesNotReplaceUsesOriginalBody() throws IOException { public InputStream responseBody() { return new ByteArrayInputStream("original-body".getBytes()) { @Override - public int read(byte[] b, int off, int len) { + public byte[] readAllBytes() { originalBodyRead.set(true); - return super.read(b, off, len); + return super.readAllBytes(); } }; } diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManagerTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManagerTest.java index f9cc9871b..1c3459c85 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManagerTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/connection/H1ConnectionManagerTest.java @@ -43,7 +43,7 @@ void tryAcquireReturnsPooledConnection() { manager.release(TEST_ROUTE, connection, false); // Need to ensure pool exists first - manager.ensurePool(TEST_ROUTE, 10); + manager.getOrCreatePool(TEST_ROUTE, 10); manager.release(TEST_ROUTE, connection, false); var result = manager.tryAcquire(TEST_ROUTE, 10); @@ -63,7 +63,7 @@ public void close() { } }; - manager.ensurePool(TEST_ROUTE, 10); + manager.getOrCreatePool(TEST_ROUTE, 10); manager.release(TEST_ROUTE, connection, false); Thread.sleep(50); // Wait longer than max idle time @@ -86,7 +86,7 @@ public boolean validateForReuse() { } }; - manager.ensurePool(TEST_ROUTE, 10); + manager.getOrCreatePool(TEST_ROUTE, 10); manager.release(TEST_ROUTE, connection, false); Thread.sleep(1100); // Wait > 1 second (VALIDATION_THRESHOLD_NANOS) @@ -108,7 +108,7 @@ public boolean isActive() { }; var validConnection = new TestConnection(); - manager.ensurePool(TEST_ROUTE, 10); + manager.getOrCreatePool(TEST_ROUTE, 10); manager.release(TEST_ROUTE, validConnection, false); manager.release(TEST_ROUTE, invalidConnection, false); @@ -136,7 +136,7 @@ public void close() { } }; - manager.ensurePool(TEST_ROUTE, 10); + manager.getOrCreatePool(TEST_ROUTE, 10); manager.release(TEST_ROUTE, invalidConnection, false); // Connection becomes inactive after being pooled active.set(false); @@ -156,7 +156,7 @@ public boolean isActive() { } }; - manager.ensurePool(TEST_ROUTE, 10); + manager.getOrCreatePool(TEST_ROUTE, 10); boolean released = manager.release(TEST_ROUTE, inactiveConnection, false); assertFalse(released, "Should not release inactive connection"); @@ -167,7 +167,7 @@ void releaseReturnsFalseWhenPoolClosed() { var manager = new H1ConnectionManager(MAX_IDLE_NANOS); var connection = new TestConnection(); - manager.ensurePool(TEST_ROUTE, 10); + manager.getOrCreatePool(TEST_ROUTE, 10); boolean released = manager.release(TEST_ROUTE, connection, true); assertFalse(released, "Should not release when pool is closed"); @@ -188,7 +188,7 @@ void removeRemovesConnectionFromPool() { var manager = new H1ConnectionManager(MAX_IDLE_NANOS); var connection = new TestConnection(); - manager.ensurePool(TEST_ROUTE, 10); + manager.getOrCreatePool(TEST_ROUTE, 10); manager.release(TEST_ROUTE, connection, false); manager.remove(TEST_ROUTE, connection); @@ -202,7 +202,7 @@ void cleanupIdleRemovesExpiredConnections() throws Exception { var manager = new H1ConnectionManager(1); // 1 nanosecond max idle var connection = new TestConnection(); - manager.ensurePool(TEST_ROUTE, 10); + manager.getOrCreatePool(TEST_ROUTE, 10); manager.release(TEST_ROUTE, connection, false); Thread.sleep(10); // Ensure connection is expired @@ -230,7 +230,7 @@ void setInactive() { } }; - manager.ensurePool(TEST_ROUTE, 10); + manager.getOrCreatePool(TEST_ROUTE, 10); manager.release(TEST_ROUTE, unhealthyConnection, false); unhealthyConnection.setInactive(); @@ -258,7 +258,7 @@ public void close() { } }; - manager.ensurePool(TEST_ROUTE, 10); + manager.getOrCreatePool(TEST_ROUTE, 10); manager.release(TEST_ROUTE, connection1, false); manager.release(TEST_ROUTE, connection2, false); @@ -269,6 +269,37 @@ public void close() { assertTrue(exceptions.isEmpty(), "No exceptions expected"); } + @Test + void getOrCreatePoolThrowsOnInconsistentMaxConnections() { + var manager = new H1ConnectionManager(MAX_IDLE_NANOS); + + manager.getOrCreatePool(TEST_ROUTE, 10); + + var ex = org.junit.jupiter.api.Assertions.assertThrows( + IllegalStateException.class, + () -> manager.getOrCreatePool(TEST_ROUTE, 20)); + + assertTrue(ex.getMessage().contains("maxConnections=10")); + assertTrue(ex.getMessage().contains("cannot change to 20")); + } + + @Test + void cleanupIdleRemovesEmptyPools() { + var manager = new H1ConnectionManager(1); // 1 nanosecond max idle + var connection = new TestConnection(); + + manager.getOrCreatePool(TEST_ROUTE, 10); + manager.release(TEST_ROUTE, connection, false); + + // First cleanup removes the expired connection + manager.cleanupIdle((conn, reason) -> {}); + + // Pool should be removed since it's empty + // Verify by checking that we can create a new pool with different maxConnections + // (would throw if old pool still existed) + manager.getOrCreatePool(TEST_ROUTE, 5); // Different maxConnections - should work + } + // Test connection implementation private static class TestConnection implements HttpConnection { @Override diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/ByteAllocatorTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/ByteAllocatorTest.java index 72574b63b..3395b1da0 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/ByteAllocatorTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/ByteAllocatorTest.java @@ -14,7 +14,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.client.ByteAllocator; class ByteAllocatorTest { From c8295316aa5d160953a71fa3f21a1c71450d76f2 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Mon, 9 Feb 2026 14:19:33 -0600 Subject: [PATCH 53/60] Improve h1 implementation and add more integ tests --- .../it/h1/ContentLengthRequestHttp11Test.java | 133 ++++++++++++++++++ .../h1/ServerCloseMidResponseHttp11Test.java | 78 ++++++++++ .../PartialResponseHttp11ClientHandler.java | 44 ++++++ .../client/UnsyncBufferedOutputStream.java | 20 ++- .../http/client/h1/ChunkedInputStream.java | 5 +- .../http/client/h1/ChunkedOutputStream.java | 50 +++---- .../http/client/h1/FailingOutputStream.java | 11 +- .../java/http/client/h1/H1Exchange.java | 2 +- .../java/http/client/h1/ProxyTunnel.java | 4 + .../client/h1/ChunkedInputStreamTest.java | 8 ++ .../client/h1/ChunkedOutputStreamTest.java | 77 +++++----- .../java/http/client/h1/ProxyTunnelTest.java | 8 +- 12 files changed, 359 insertions(+), 81 deletions(-) create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ContentLengthRequestHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ServerCloseMidResponseHttp11Test.java create mode 100644 http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/PartialResponseHttp11ClientHandler.java diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ContentLengthRequestHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ContentLengthRequestHttp11Test.java new file mode 100644 index 000000000..a364fbb2f --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ContentLengthRequestHttp11Test.java @@ -0,0 +1,133 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.util.CharsetUtil; +import java.io.ByteArrayOutputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.Http11ClientHandler; +import software.amazon.smithy.java.io.datastream.DataStream; + +/** + * Tests HTTP/1.1 request body with Content-Length (non-chunked). + */ +public class ContentLengthRequestHttp11Test extends BaseHttpClientIntegTest { + + private CapturingHandler handler; + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + handler = new CapturingHandler(); + return builder + .httpVersion(software.amazon.smithy.java.http.api.HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> handler); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder.httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1); + } + + @Test + void sendsRequestBodyWithContentLength() throws Exception { + var headers = HttpHeaders.ofModifiable(); + headers.addHeader("content-type", "text/plain"); + headers.addHeader("content-length", String.valueOf(REQUEST_CONTENTS.length())); + + var request = HttpRequest.builder() + .httpVersion(software.amazon.smithy.java.http.api.HttpVersion.HTTP_1_1) + .uri(new URI(uri())) + .method("POST") + .headers(headers) + .body(DataStream.ofString(REQUEST_CONTENTS)) + .build(); + + var response = client.send(request); + + assertEquals(200, response.statusCode()); + assertEquals(RESPONSE_CONTENTS, readBody(response)); + assertEquals(REQUEST_CONTENTS, handler.capturedBody.toString(StandardCharsets.UTF_8)); + + // Verify Content-Length was sent (not chunked) + assertTrue(handler.capturedHeaders.containsKey("content-length") + || handler.capturedHeaders.containsKey("Content-Length")); + } + + @Test + void sendsLargeRequestBodyWithContentLength() throws Exception { + // 100KB body + String largeBody = "x".repeat(100 * 1024); + + var headers = HttpHeaders.ofModifiable(); + headers.addHeader("content-type", "text/plain"); + headers.addHeader("content-length", String.valueOf(largeBody.length())); + + var request = HttpRequest.builder() + .httpVersion(software.amazon.smithy.java.http.api.HttpVersion.HTTP_1_1) + .uri(new URI(uri())) + .method("POST") + .headers(headers) + .body(DataStream.ofString(largeBody)) + .build(); + + var response = client.send(request); + + assertEquals(200, response.statusCode()); + assertEquals(largeBody, handler.capturedBody.toString(StandardCharsets.UTF_8)); + } + + static class CapturingHandler implements Http11ClientHandler { + final Map> capturedHeaders = new HashMap<>(); + final ByteArrayOutputStream capturedBody = new ByteArrayOutputStream(); + + @Override + public void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request) { + // Capture headers + for (var entry : request.headers()) { + capturedHeaders.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).add(entry.getValue()); + } + // Capture body + var content = request.content(); + var bytes = new byte[content.readableBytes()]; + content.getBytes(content.readerIndex(), bytes); + try { + capturedBody.write(bytes); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // Send response + var responseContent = Unpooled.copiedBuffer(RESPONSE_CONTENTS, CharsetUtil.UTF_8); + var response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, responseContent); + response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); + response.headers().set(HttpHeaderNames.CONTENT_LENGTH, responseContent.readableBytes()); + response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); + ctx.writeAndFlush(response); + } + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ServerCloseMidResponseHttp11Test.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ServerCloseMidResponseHttp11Test.java new file mode 100644 index 000000000..fa2404983 --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/h1/ServerCloseMidResponseHttp11Test.java @@ -0,0 +1,78 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.h1; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.api.HttpVersion; +import software.amazon.smithy.java.http.client.connection.CloseReason; +import software.amazon.smithy.java.http.client.connection.ConnectionPoolListener; +import software.amazon.smithy.java.http.client.connection.HttpConnection; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPoolBuilder; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; +import software.amazon.smithy.java.http.client.it.server.NettyTestServer; +import software.amazon.smithy.java.http.client.it.server.h1.PartialResponseHttp11ClientHandler; + +/** + * Tests client handling when server closes connection mid-response. + */ +public class ServerCloseMidResponseHttp11Test extends BaseHttpClientIntegTest { + + private final AtomicInteger connectCount = new AtomicInteger(); + private final AtomicInteger closeCount = new AtomicInteger(); + + @Override + protected NettyTestServer.Builder configureServer(NettyTestServer.Builder builder) { + // Server advertises 1000 bytes but only sends 10, then closes + return builder + .httpVersion(HttpVersion.HTTP_1_1) + .http11HandlerFactory(ctx -> new PartialResponseHttp11ClientHandler("partial...", 1000)); + } + + @Override + protected HttpConnectionPoolBuilder configurePool(HttpConnectionPoolBuilder builder) { + return builder + .httpVersionPolicy(HttpVersionPolicy.ENFORCE_HTTP_1_1) + .addListener(new ConnectionPoolListener() { + @Override + public void onConnected(HttpConnection conn) { + connectCount.incrementAndGet(); + } + + @Override + public void onClosed(HttpConnection conn, CloseReason reason) { + closeCount.incrementAndGet(); + } + }); + } + + @Test + void throwsWhenServerClosesBeforeFullResponse() throws Exception { + var request = plainTextRequest(HttpVersion.HTTP_1_1, ""); + + var response = client.send(request); + + // Reading the body should fail because server closed before sending full content + var ex = assertThrows(IOException.class, () -> readBody(response)); + assertTrue( + ex.getMessage().contains("end of stream") + || ex.getMessage().contains("EOF") + || ex.getMessage().contains("closed") + || ex.getMessage().contains("Unexpected"), + "Expected EOF-related error, got: " + ex.getMessage()); + + // Connection should have been created + assertEquals(1, connectCount.get(), "Should have created 1 connection"); + + // Connection should be closed/evicted, not returned to pool + assertEquals(1, closeCount.get(), "Connection should be closed after truncated response"); + } +} diff --git a/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/PartialResponseHttp11ClientHandler.java b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/PartialResponseHttp11ClientHandler.java new file mode 100644 index 000000000..ffc304d9b --- /dev/null +++ b/http/http-client/src/it/java/software/amazon/smithy/java/http/client/it/server/h1/PartialResponseHttp11ClientHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.it.server.h1; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultHttpContent; +import io.netty.handler.codec.http.DefaultHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import java.nio.charset.StandardCharsets; + +/** + * Handler that sends partial response then closes connection. + * Used to test client handling of server closing mid-response. + */ +public class PartialResponseHttp11ClientHandler implements Http11ClientHandler { + private final String partialBody; + private final int advertisedLength; + + public PartialResponseHttp11ClientHandler(String partialBody, int advertisedLength) { + this.partialBody = partialBody; + this.advertisedLength = advertisedLength; + } + + @Override + public void onFullRequest(ChannelHandlerContext ctx, FullHttpRequest request) { + var response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); + response.headers().set("content-length", advertisedLength); + response.headers().set("content-type", "text/plain"); + ctx.write(response); + + // Send partial body (less than advertised) + ctx.write(new DefaultHttpContent(Unpooled.copiedBuffer(partialBody, StandardCharsets.UTF_8))); + ctx.flush(); + + // Close connection without sending full body + ctx.close(); + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStream.java index bd9f77348..30fb1da5e 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/UnsyncBufferedOutputStream.java @@ -96,23 +96,35 @@ public void writeAscii(String s) throws IOException { return; } + // Fast path: string fits in remaining buffer + int available = buf.length - pos; + if (len <= available) { + s.getBytes(0, len, buf, pos); + pos += len; + return; + } + + // Slow path: string spans buffer boundary + writeAsciiSlow(s, len, available); + } + + @SuppressWarnings("deprecation") + private void writeAsciiSlow(String s, int len, int available) throws IOException { int stringPosition = 0; int bufLen = buf.length; - // Work through the string bytes in chunks of bytes that can fit into the buffer. + // Work through the string in chunks that fit into the buffer while (stringPosition < len) { - int available = bufLen - pos; if (available == 0) { - // We filled up the buffer, so flush and then continue to copy the next chunk flushBuffer(); available = bufLen; } - // Copy as many characters as will fit (or remaining string length, whichever is smaller) int toCopy = Math.min(available, len - stringPosition); s.getBytes(stringPosition, stringPosition + toCopy, buf, pos); pos += toCopy; stringPosition += toCopy; + available = bufLen - pos; } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java index 36bc0d4da..4dc468f37 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java @@ -149,7 +149,6 @@ public void close() throws IOException { // Drain remaining chunks to allow connection reuse (before setting closed flag) if (!eof) { - // use transferTo from delegate since it's optimized to not allocate transferTo(OutputStream.nullOutputStream()); } @@ -231,6 +230,10 @@ private static long parseHex(byte[] buf, int start, int end) throws IOException } else { throw new IOException("Invalid hex character in chunk size: " + (char) b); } + // Check for overflow before shifting (top 4 bits must be clear) + if ((value & 0xF000_0000_0000_0000L) != 0) { + throw new IOException("HTTP/1.1 chunk size overflow"); + } value = (value << 4) | digit; } return value; diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStream.java index 5c2d67068..0eafd093f 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStream.java @@ -7,8 +7,8 @@ import java.io.IOException; import java.io.OutputStream; -import java.nio.charset.StandardCharsets; import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.client.UnsyncBufferedOutputStream; /** * OutputStream that writes HTTP/1.1 chunked transfer encoding format (RFC 7230 Section 4.1). @@ -17,7 +17,7 @@ * for subsequent HTTP/1.1 requests. The socket lifecycle is managed by {@link H1Connection}. */ final class ChunkedOutputStream extends OutputStream { - private final OutputStream delegate; + private final UnsyncBufferedOutputStream delegate; private final byte[] buffer; private int bufferPos = 0; private boolean closed = false; @@ -29,17 +29,17 @@ final class ChunkedOutputStream extends OutputStream { /** * Create a ChunkedOutputStream with default chunk size (8KB). */ - ChunkedOutputStream(OutputStream delegate) { + ChunkedOutputStream(UnsyncBufferedOutputStream delegate) { this(delegate, DEFAULT_CHUNK_SIZE); } /** * Create a ChunkedOutputStream with specified chunk size. * - * @param delegate underlying stream to write chunks to + * @param delegate underlying buffered stream to write chunks to * @param chunkSize maximum size of each chunk in bytes (must be > 0) */ - ChunkedOutputStream(OutputStream delegate, int chunkSize) { + ChunkedOutputStream(UnsyncBufferedOutputStream delegate, int chunkSize) { if (delegate == null) { throw new NullPointerException("delegate"); } else if (chunkSize <= 0) { @@ -156,53 +156,35 @@ private void flushChunk() throws IOException { /** * Write a chunk with the given data. * - *

        Format: - * {size-in-hex}\r\n - * {data}\r\n + *

        Format: {size-in-hex}\r\n{data}\r\n */ private void writeChunk(byte[] data, int off, int len) throws IOException { - // Write chunk size in hexadecimal - String hexSize = Integer.toHexString(len); - delegate.write(hexSize.getBytes(StandardCharsets.US_ASCII)); - delegate.write('\r'); - delegate.write('\n'); - - // Write chunk data + delegate.writeAscii(Integer.toHexString(len)); + delegate.writeAscii("\r\n"); delegate.write(data, off, len); - - // Write trailing CRLF - delegate.write('\r'); - delegate.write('\n'); + delegate.writeAscii("\r\n"); } /** * Write the final 0-sized chunk with optional trailers. * - *

        Format: - * 0\r\n - * [trailer-name: trailer-value\r\n]* - * \r\n + *

        Format: 0\r\n[trailer-name: trailer-value\r\n]*\r\n */ private void writeFinalChunk() throws IOException { - delegate.write('0'); - delegate.write('\r'); - delegate.write('\n'); + delegate.writeAscii("0\r\n"); if (trailers != null) { for (var entry : trailers) { String name = entry.getKey(); for (String value : entry.getValue()) { - delegate.write(name.getBytes(StandardCharsets.US_ASCII)); - delegate.write(':'); - delegate.write(' '); - delegate.write(value.getBytes(StandardCharsets.US_ASCII)); - delegate.write('\r'); - delegate.write('\n'); + delegate.writeAscii(name); + delegate.writeAscii(": "); + delegate.writeAscii(value); + delegate.writeAscii("\r\n"); } } } - delegate.write('\r'); - delegate.write('\n'); + delegate.writeAscii("\r\n"); } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/FailingOutputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/FailingOutputStream.java index ab5b2a3fa..7c2f3020d 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/FailingOutputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/FailingOutputStream.java @@ -9,10 +9,13 @@ import java.io.OutputStream; /** - * OutputStream that immediately throws a pre-existing exception on any operation. + * OutputStream that immediately throws a pre-existing exception on any write operation. * Used when Expect: 100-continue fails before body transmission. + * + *

        Close is a no-op since there's nothing to clean up. The exception is thrown when the caller attempts to write, + * not when they clean up. */ -class FailingOutputStream extends OutputStream { +final class FailingOutputStream extends OutputStream { private final IOException exception; FailingOutputStream(IOException exception) { @@ -30,7 +33,7 @@ public void flush() throws IOException { } @Override - public void close() throws IOException { - throw exception; + public void close() { + // No-op: nothing to close } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java index 16f6cf7b9..1e84ac1a3 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/H1Exchange.java @@ -116,7 +116,7 @@ public HttpRequest request() { @Override public OutputStream requestBody() { if (requestOut == null) { - OutputStream socketOut = connection.getOutputStream(); + UnsyncBufferedOutputStream socketOut = connection.getOutputStream(); var headers = request.headers(); // Handle Expect: 100-continue before creating output stream diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ProxyTunnel.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ProxyTunnel.java index a6f1f8d2c..cb4f42280 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ProxyTunnel.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ProxyTunnel.java @@ -6,6 +6,7 @@ package software.amazon.smithy.java.http.client.h1; import java.io.IOException; +import java.io.OutputStream; import java.net.Socket; import java.net.URI; import java.time.Duration; @@ -88,6 +89,9 @@ public static Result establish( return new Result(proxySocket, status, headers); } + // Drain response body to prepare connection for next request (e.g., 407 retry) + exchange.responseBody().transferTo(OutputStream.nullOutputStream()); + priorResponse = HttpResponse.builder() .statusCode(status) .headers(headers) diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStreamTest.java index 82c335566..2fa545407 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStreamTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStreamTest.java @@ -309,6 +309,14 @@ void throwsOnChunkSizeExceedsMax() { assertThrows(IOException.class, stream::read); } + @Test + void throwsOnChunkSizeOverflow() { + // 17 hex digits would overflow a long (max is 16 hex digits = 64 bits) + var stream = chunked("FFFFFFFFFFFFFFFFF\r\n"); + + assertThrows(IOException.class, stream::read); + } + @Test void throwsOnInvalidTrailerLine() { // Trailer line without colon is invalid diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStreamTest.java index efbf66b6c..01c69d1df 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStreamTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedOutputStreamTest.java @@ -15,75 +15,84 @@ import java.util.Map; import org.junit.jupiter.api.Test; import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.client.UnsyncBufferedOutputStream; class ChunkedOutputStreamTest { + private ChunkedOutputStream chunked(ByteArrayOutputStream baos, int chunkSize) { + return new ChunkedOutputStream(new UnsyncBufferedOutputStream(baos, 1024), chunkSize); + } + + private ChunkedOutputStream chunked(ByteArrayOutputStream baos) { + return new ChunkedOutputStream(new UnsyncBufferedOutputStream(baos, 1024)); + } + @Test void writesSingleChunk() throws IOException { - var delegate = new ByteArrayOutputStream(); - var stream = new ChunkedOutputStream(delegate, 1024); + var baos = new ByteArrayOutputStream(); + var stream = chunked(baos, 1024); stream.write("hello".getBytes()); stream.close(); - assertEquals("5\r\nhello\r\n0\r\n\r\n", delegate.toString()); + assertEquals("5\r\nhello\r\n0\r\n\r\n", baos.toString()); } @Test void writesMultipleChunks() throws IOException { - var delegate = new ByteArrayOutputStream(); - var stream = new ChunkedOutputStream(delegate, 5); + var baos = new ByteArrayOutputStream(); + var stream = chunked(baos, 5); stream.write("hello world".getBytes()); stream.close(); - assertEquals("5\r\nhello\r\n5\r\n worl\r\n1\r\nd\r\n0\r\n\r\n", delegate.toString()); + assertEquals("5\r\nhello\r\n5\r\n worl\r\n1\r\nd\r\n0\r\n\r\n", baos.toString()); } @Test void writesEmptyBody() throws IOException { - var delegate = new ByteArrayOutputStream(); - var stream = new ChunkedOutputStream(delegate); + var baos = new ByteArrayOutputStream(); + var stream = chunked(baos); stream.close(); - assertEquals("0\r\n\r\n", delegate.toString()); + assertEquals("0\r\n\r\n", baos.toString()); } @Test void writesSingleBytes() throws IOException { - var delegate = new ByteArrayOutputStream(); - var stream = new ChunkedOutputStream(delegate, 1024); + var baos = new ByteArrayOutputStream(); + var stream = chunked(baos, 1024); stream.write('a'); stream.write('b'); stream.write('c'); stream.close(); - assertEquals("3\r\nabc\r\n0\r\n\r\n", delegate.toString()); + assertEquals("3\r\nabc\r\n0\r\n\r\n", baos.toString()); } @Test void flushWritesChunk() throws IOException { - var delegate = new ByteArrayOutputStream(); - var stream = new ChunkedOutputStream(delegate, 1024); + var baos = new ByteArrayOutputStream(); + var stream = chunked(baos, 1024); stream.write("hello".getBytes()); stream.flush(); - assertEquals("5\r\nhello\r\n", delegate.toString()); + assertEquals("5\r\nhello\r\n", baos.toString()); } @Test void writesTrailers() throws IOException { - var delegate = new ByteArrayOutputStream(); - var stream = new ChunkedOutputStream(delegate, 1024); + var baos = new ByteArrayOutputStream(); + var stream = chunked(baos, 1024); stream.write("hello".getBytes()); stream.setTrailers(HttpHeaders.of(Map.of("x-checksum", List.of("abc123")))); stream.close(); - assertEquals("5\r\nhello\r\n0\r\nx-checksum: abc123\r\n\r\n", delegate.toString()); + assertEquals("5\r\nhello\r\n0\r\nx-checksum: abc123\r\n\r\n", baos.toString()); } @Test void writesMultipleTrailers() throws IOException { - var delegate = new ByteArrayOutputStream(); - var stream = new ChunkedOutputStream(delegate, 1024); + var baos = new ByteArrayOutputStream(); + var stream = chunked(baos, 1024); stream.write("hi".getBytes()); stream.setTrailers(HttpHeaders.of(Map.of( "x-foo", @@ -92,7 +101,7 @@ void writesMultipleTrailers() throws IOException { List.of("qux")))); stream.close(); - String result = delegate.toString(); + String result = baos.toString(); assertTrue(result.startsWith("2\r\nhi\r\n0\r\n")); assertTrue(result.contains("x-foo: bar\r\n")); assertTrue(result.contains("x-baz: qux\r\n")); @@ -101,43 +110,44 @@ void writesMultipleTrailers() throws IOException { @Test void writesMultiValueTrailer() throws IOException { - var delegate = new ByteArrayOutputStream(); - var stream = new ChunkedOutputStream(delegate, 1024); + var baos = new ByteArrayOutputStream(); + var stream = chunked(baos, 1024); stream.write("hi".getBytes()); stream.setTrailers(HttpHeaders.of(Map.of("x-multi", List.of("a", "b")))); stream.close(); - String result = delegate.toString(); + String result = baos.toString(); assertTrue(result.contains("x-multi: a\r\n")); assertTrue(result.contains("x-multi: b\r\n")); } @Test void singleByteWriteFlushesWhenBufferFull() throws IOException { - var delegate = new ByteArrayOutputStream(); - var stream = new ChunkedOutputStream(delegate, 3); + var baos = new ByteArrayOutputStream(); + var stream = chunked(baos, 3); stream.write('a'); stream.write('b'); stream.write('c'); // buffer full, triggers flush + stream.flush(); // need to flush the underlying buffer too - assertEquals("3\r\nabc\r\n", delegate.toString()); + assertEquals("3\r\nabc\r\n", baos.toString()); } @Test void closeIsIdempotent() throws IOException { - var delegate = new ByteArrayOutputStream(); - var stream = new ChunkedOutputStream(delegate); + var baos = new ByteArrayOutputStream(); + var stream = chunked(baos); stream.write("hi".getBytes()); stream.close(); stream.close(); - assertEquals("2\r\nhi\r\n0\r\n\r\n", delegate.toString()); + assertEquals("2\r\nhi\r\n0\r\n\r\n", baos.toString()); } @Test void throwsAfterClose() throws IOException { - var delegate = new ByteArrayOutputStream(); - var stream = new ChunkedOutputStream(delegate); + var baos = new ByteArrayOutputStream(); + var stream = chunked(baos); stream.close(); assertThrows(IOException.class, () -> stream.write(1)); @@ -145,7 +155,8 @@ void throwsAfterClose() throws IOException { @Test void throwsOnInvalidChunkSize() { - var delegate = new ByteArrayOutputStream(); + var baos = new ByteArrayOutputStream(); + var delegate = new UnsyncBufferedOutputStream(baos, 1024); assertThrows(IllegalArgumentException.class, () -> new ChunkedOutputStream(delegate, 0)); } diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ProxyTunnelTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ProxyTunnelTest.java index ff41401f3..a04a16356 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ProxyTunnelTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ProxyTunnelTest.java @@ -45,7 +45,7 @@ void establishSuccessfulTunnel() throws IOException { @Test void tunnelFailsWithForbidden() throws IOException { - var socket = new FakeSocket("HTTP/1.1 403 Forbidden\r\n\r\n"); + var socket = new FakeSocket("HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\n"); var result = ProxyTunnel.establish(socket, "example.com", 443, null, TIMEOUT); assertNull(result.socket()); @@ -67,7 +67,7 @@ void tunnelWithBasicAuth() throws IOException { @Test void tunnelAuthFailsAfter407() throws IOException { - var socket = new FakeSocket("HTTP/1.1 407 Proxy Authentication Required\r\n\r\n"); + var socket = new FakeSocket("HTTP/1.1 407 Proxy Authentication Required\r\nContent-Length: 0\r\n\r\n"); var creds = new HttpCredentials.Basic("user", "pass", true); var result = ProxyTunnel.establish(socket, "example.com", 443, creds, TIMEOUT); @@ -78,7 +78,7 @@ void tunnelAuthFailsAfter407() throws IOException { @Test void tunnelWithMultiRoundAuth() throws IOException { var socket = new FakeSocket( - "HTTP/1.1 407 Proxy Authentication Required\r\n\r\n" + + "HTTP/1.1 407 Proxy Authentication Required\r\nContent-Length: 0\r\n\r\n" + "HTTP/1.1 200 Connection Established\r\n\r\n"); var creds = new MultiRoundCredentials(); var result = ProxyTunnel.establish(socket, "example.com", 443, creds, TIMEOUT); @@ -90,7 +90,7 @@ void tunnelWithMultiRoundAuth() throws IOException { @Test void tunnelWithoutCredentialsOn407() throws IOException { - var socket = new FakeSocket("HTTP/1.1 407 Proxy Authentication Required\r\n\r\n"); + var socket = new FakeSocket("HTTP/1.1 407 Proxy Authentication Required\r\nContent-Length: 0\r\n\r\n"); var result = ProxyTunnel.establish(socket, "example.com", 443, null, TIMEOUT); assertNull(result.socket()); From 7b7a938c3c23bc27faf1122317ece5baa477d564 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Mon, 9 Feb 2026 16:10:10 -0600 Subject: [PATCH 54/60] Make h2 code cleanup, add registry bench --- .../client/h2/StreamRegistryBenchmark.java | 109 ++++++++++++++++++ .../smithy/java/http/client/h2/DataChunk.java | 6 +- .../http/client/h2/FlowControlWindow.java | 4 +- .../java/http/client/h2/H2Connection.java | 26 ++++- .../http/client/h2/H2DataInputStream.java | 8 +- .../http/client/h2/H2DataOutputStream.java | 7 ++ .../smithy/java/http/client/h2/H2Muxer.java | 7 +- .../java/http/client/h2/H2MuxerWorkItem.java | 16 --- .../java/http/client/h2/H2StreamState.java | 2 +- .../client/h1/FailingOutputStreamTest.java | 6 +- 10 files changed, 153 insertions(+), 38 deletions(-) create mode 100644 http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/h2/StreamRegistryBenchmark.java diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/h2/StreamRegistryBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/h2/StreamRegistryBenchmark.java new file mode 100644 index 000000000..11cfd144d --- /dev/null +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/h2/StreamRegistryBenchmark.java @@ -0,0 +1,109 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReferenceArray; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; + +/** + * Microbenchmark comparing StreamRegistry-style lookup vs ConcurrentHashMap. + * + *

        Run with: ./gradlew :http:http-client:jmh -Pjmh.includes="StreamRegistryBenchmark" + */ +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(value = 2, jvmArgs = {"-Xms1g", "-Xmx1g"}) +@State(Scope.Benchmark) +public class StreamRegistryBenchmark { + + @Param({"10", "100", "1000"}) + private int activeStreams; + + // StreamRegistry-style array lookup + private static final int SLOTS = 4096; + private static final int SLOT_MASK = SLOTS - 1; + private AtomicReferenceArray array; + + // CHM baseline + private ConcurrentHashMap chm; + + private int[] streamIds; + + static final class Entry { + final int streamId; + Entry(int streamId) { + this.streamId = streamId; + } + } + + private static int slot(int streamId) { + return ((streamId - 1) >>> 1) & SLOT_MASK; + } + + private Entry arrayGet(int streamId) { + Entry e = array.get(slot(streamId)); + return (e != null && e.streamId == streamId) ? e : null; + } + + @State(Scope.Thread) + public static class ThreadState { + int index; + } + + @Setup + public void setup() { + array = new AtomicReferenceArray<>(SLOTS); + chm = new ConcurrentHashMap<>(); + streamIds = new int[activeStreams]; + + for (int i = 0; i < activeStreams; i++) { + int streamId = 2 * i + 1; // HTTP/2 client stream IDs: 1, 3, 5, ... + streamIds[i] = streamId; + Entry e = new Entry(streamId); + array.set(slot(streamId), e); + chm.put(streamId, e); + } + } + + @Benchmark + @Threads(1) + public Entry arrayGet_1t(ThreadState ts) { + return arrayGet(streamIds[ts.index++ % activeStreams]); + } + + @Benchmark + @Threads(1) + public Entry chmGet_1t(ThreadState ts) { + return chm.get(streamIds[ts.index++ % activeStreams]); + } + + @Benchmark + @Threads(4) + public Entry arrayGet_4t(ThreadState ts) { + return arrayGet(streamIds[ts.index++ % activeStreams]); + } + + @Benchmark + @Threads(4) + public Entry chmGet_4t(ThreadState ts) { + return chm.get(streamIds[ts.index++ % activeStreams]); + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/DataChunk.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/DataChunk.java index e17b3b3c4..c5a2cc492 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/DataChunk.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/DataChunk.java @@ -6,6 +6,10 @@ package software.amazon.smithy.java.http.client.h2; /** - * Data chunk containing a buffer and metadata. + * Data chunk from an HTTP/2 DATA frame. + * + * @param data buffer containing frame data (ownership transferred to consumer) + * @param length actual data length (can be less than {@code data.length}) + * @param endStream true if this is the final chunk (END_STREAM flag was set) */ record DataChunk(byte[] data, int length, boolean endStream) {} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java index a3a83f520..c262a2b50 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/FlowControlWindow.java @@ -82,9 +82,7 @@ int tryAcquireUpTo(int maxBytes, long timeoutMs) throws InterruptedException { } // Use short poll interval instead of full timeout long waitNs = Math.min(remainingNs, POLL_INTERVAL_NS); - long before = System.nanoTime(); - available.awaitNanos(waitNs); - remainingNs -= (System.nanoTime() - before); + remainingNs = available.awaitNanos(waitNs); } int acquired = (int) Math.min(window, maxBytes); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java index 805e386c6..2fc92bf8e 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java @@ -505,18 +505,34 @@ private void receiveServerPreface() throws IOException { * Try to receive the initial connection-level WINDOW_UPDATE that servers typically send * right after SETTINGS to expand the connection flow control window. * Uses a short timeout - if no frame arrives quickly, we proceed anyway. + * + *

        At this point in the handshake, the only valid frames the server can send are: + *

          + *
        • WINDOW_UPDATE - what we're looking for, apply it
        • + *
        • SETTINGS ACK - acknowledgment of our SETTINGS, safe to ignore
        • + *
        + * Any other frame type is a protocol error. */ private void receiveInitialWindowUpdate() throws IOException { int originalTimeout = socket.getSoTimeout(); try { socket.setSoTimeout(50); // Short timeout - don't block long if server doesn't send one - int type = frameCodec.nextFrame(); - if (type == FRAME_TYPE_WINDOW_UPDATE && frameCodec.frameStreamId() == 0) { - int increment = frameCodec.readAndParseWindowUpdate(); - muxer.releaseConnectionWindow(increment); + switch (type) { + case -1, FRAME_TYPE_SETTINGS: + // EOF or SETTINGS ACK, ignore, we don't wait for it + break; + case FRAME_TYPE_WINDOW_UPDATE: + if (frameCodec.frameStreamId() == 0) { + int increment = frameCodec.readAndParseWindowUpdate(); + muxer.releaseConnectionWindow(increment); + } + break; + default: + throw new H2Exception( + ERROR_PROTOCOL_ERROR, + "Unexpected frame during handshake: " + H2Constants.frameTypeName(type)); } - // Any other frame type is ignored - we only act on connection-level WINDOW_UPDATE here. } catch (SocketTimeoutException e) { // No initial WINDOW_UPDATE - that's fine, proceed with default window } finally { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java index 19b994d82..3c91143df 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataInputStream.java @@ -207,6 +207,8 @@ public long transferTo(OutputStream out) throws IOException { } // Pull and write chunks directly - no intermediate buffer, no double copy + // Note: pullNextChunk() returns the previous buffer to pool before getting next, + // so when it returns false (EOF), currentBuffer is already null. while (pullNextChunk()) { out.write(currentBuffer, 0, currentLength); transferred += currentLength; @@ -214,12 +216,6 @@ public long transferTo(OutputStream out) throws IOException { readPosition = currentLength; } - // Return the last buffer (pullNextChunk returned false but buffer may still be set) - if (currentBuffer != null) { - bufferReturner.accept(currentBuffer); - currentBuffer = null; - } - return transferred; } } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataOutputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataOutputStream.java index cb86f2992..7e84a96cb 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataOutputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2DataOutputStream.java @@ -31,7 +31,10 @@ final class H2DataOutputStream extends OutputStream { public void write(int b) throws IOException { if (closed) { throw new IOException("Stream closed"); + } else if (buffer.length == 0) { + throw new IOException("Cannot write body: END_STREAM already sent with headers"); } + buffer[pos++] = (byte) b; if (pos >= buffer.length) { flush(); @@ -44,6 +47,10 @@ public void write(byte[] b, int off, int len) throws IOException { throw new IndexOutOfBoundsException(); } else if (closed) { throw new IOException("Stream closed"); + } else if (len == 0) { + return; + } else if (buffer.length == 0) { + throw new IOException("Cannot write body: END_STREAM already sent with headers"); } // Fast path: large write - flush buffer if needed, then write directly diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java index 83b6f3362..a11bb0262 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java @@ -672,7 +672,10 @@ private void processExchangePendingWrites(H2Exchange exchange) { // and re-enqueues while still processing. exchange.inWorkQueue = false; - if (!exchange.pendingWrites.isEmpty() && !exchange.inWorkQueue) { + // Check if more writes arrived while we were draining. If so, re-enqueue. + // Note: there's a benign race where VT could also enqueue via CAS, causing + // a duplicate entry - but processExchangePendingWrites handles empty queues fine. + if (!exchange.pendingWrites.isEmpty()) { exchange.inWorkQueue = true; dataWorkQueue.offer(exchange); } @@ -703,8 +706,6 @@ private void processBatch(ArrayList batch) { private void processItem(H2MuxerWorkItem item) throws IOException { switch (item) { case H2MuxerWorkItem.EncodeHeaders h -> processEncodeHeaders(h); - case H2MuxerWorkItem.WriteData d -> - frameCodec.writeFrame(FRAME_TYPE_DATA, d.flags, d.streamId, d.data, d.offset, d.length); case H2MuxerWorkItem.WriteTrailers t -> processWriteTrailers(t); case H2MuxerWorkItem.WriteRst r -> frameCodec.writeRstStream(r.streamId, r.errorCode); case H2MuxerWorkItem.WriteGoaway g -> frameCodec.writeGoaway(g.lastStreamId, g.errorCode, g.debugData); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2MuxerWorkItem.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2MuxerWorkItem.java index cebd97d53..62a73894a 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2MuxerWorkItem.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2MuxerWorkItem.java @@ -31,22 +31,6 @@ static final class EncodeHeaders extends H2MuxerWorkItem { } } - static final class WriteData extends H2MuxerWorkItem { - final int streamId; - final byte[] data; - final int offset; - final int length; - final int flags; - - WriteData(int streamId, byte[] data, int offset, int length, int flags) { - this.streamId = streamId; - this.data = data; - this.offset = offset; - this.length = length; - this.flags = flags; - } - } - static final class WriteTrailers extends H2MuxerWorkItem { final int streamId; final HttpHeaders trailers; diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamState.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamState.java index 9cb69864d..96abee6a9 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamState.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2StreamState.java @@ -151,7 +151,7 @@ int getStatusCode() { * Atomically set response headers received with status code. * Transitions read state from WAITING to READING if appropriate. * - * @param statusCode the HTTP status code (100-999) + * @param statusCode the HTTP status code (100-599) */ void setResponseHeadersReceived(int statusCode) { for (;;) { diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/FailingOutputStreamTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/FailingOutputStreamTest.java index 91e40c75e..5ae72531a 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/FailingOutputStreamTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/FailingOutputStreamTest.java @@ -32,11 +32,11 @@ void flushThrowsConfiguredException() { } @Test - void closeThrowsConfiguredException() { + void closeIsNoOp() throws IOException { var expected = new IOException("test error"); var stream = new FailingOutputStream(expected); - var thrown = assertThrows(IOException.class, stream::close); - assertSame(expected, thrown); + // close() should not throw - it's a no-op to avoid masking the real error + stream.close(); } } From 78b85aff2f29f948f9f38f26ad3acf055b24977a Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 12 Feb 2026 10:48:16 -0600 Subject: [PATCH 55/60] Add h2 tests --- .../java/http/client/h2/H2Exchange.java | 3 - .../h2/H2ResponseHeaderProcessorTest.java | 199 ++++++++++++++++++ .../http/client/h2/StreamRegistryTest.java | 188 +++++++++++++++++ .../http/client/h2/hpack/HuffmanTest.java | 64 ++++++ .../http/client/h2/hpack/StaticTableTest.java | 117 ++++++++++ 5 files changed, 568 insertions(+), 3 deletions(-) create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessorTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/StreamRegistryTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HuffmanTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTableTest.java diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index 62c4e8f2d..659ac04f1 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -36,7 +36,6 @@ import software.amazon.smithy.java.http.client.DelegatedClosingOutputStream; import software.amazon.smithy.java.http.client.HttpExchange; import software.amazon.smithy.java.http.client.h2.hpack.HeaderField; -import software.amazon.smithy.java.logging.InternalLogger; /** * HTTP/2 exchange implementation for a single stream with multiplexing support. @@ -62,8 +61,6 @@ */ public final class H2Exchange implements HttpExchange { - private static final InternalLogger LOGGER = InternalLogger.getLogger(H2Exchange.class); - // Max frames to acquire flow control for in a single batch (64 frames = 1MB at default 16KB frame size) private static final int FLOW_CONTROL_BATCH_FRAMES = 64; diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessorTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessorTest.java new file mode 100644 index 000000000..ede5e48df --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessorTest.java @@ -0,0 +1,199 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.http.client.h2.hpack.HeaderField; + +class H2ResponseHeaderProcessorTest { + + private static HeaderField hf(String name, String value) { + return new HeaderField(name, value); + } + + @Test + void validResponseHeaders() throws IOException { + var fields = List.of( + hf(":status", "200"), + hf("content-type", "application/json"), + hf("content-length", "42")); + + var result = H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false); + + assertEquals(200, result.statusCode()); + assertEquals(42, result.contentLength()); + assertEquals("application/json", result.headers().firstValue("content-type")); + } + + @Test + void informationalResponse() throws IOException { + var fields = List.of(hf(":status", "100")); + var result = H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false); + + assertTrue(result.isInformational()); + } + + @Test + void informationalResponseWithEndStreamThrows() { + var fields = List.of(hf(":status", "100")); + var ex = assertThrows(H2Exception.class, + () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, true)); + + assertTrue(ex.getMessage().contains("1xx response must not have END_STREAM")); + } + + @Test + void missingStatusThrows() { + var fields = List.of(hf("content-type", "text/plain")); + + assertThrows(IOException.class, + () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); + } + + @Test + void duplicateStatusThrows() { + var fields = List.of(hf(":status", "200"), hf(":status", "201")); + var ex = assertThrows(H2Exception.class, + () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); + + assertTrue(ex.getMessage().contains("single :status")); + } + + @Test + void invalidStatusValueThrows() { + var fields = List.of(hf(":status", "abc")); + + assertThrows(IOException.class, + () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); + } + + @Test + void pseudoHeaderAfterRegularHeaderThrows() { + var fields = List.of( + hf(":status", "200"), + hf("content-type", "text/plain"), + hf(":unknown", "value")); + var ex = assertThrows(H2Exception.class, + () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); + + assertTrue(ex.getMessage().contains("appears after regular header")); + } + + @Test + void requestPseudoHeaderInResponseThrows() { + var fields = List.of(hf(":status", "200"), hf(":method", "GET")); + var ex = assertThrows(H2Exception.class, + () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); + + assertTrue(ex.getMessage().contains("Request pseudo-header")); + } + + @Test + void unknownPseudoHeaderThrows() { + var fields = List.of(hf(":status", "200"), hf(":unknown", "value")); + var ex = assertThrows(H2Exception.class, + () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); + + assertTrue(ex.getMessage().contains("Unknown pseudo-header")); + } + + @Test + void invalidContentLengthThrows() { + var fields = List.of(hf(":status", "200"), hf("content-length", "not-a-number")); + var ex = assertThrows(H2Exception.class, + () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); + + assertTrue(ex.getMessage().contains("Invalid Content-Length")); + } + + @Test + void multipleConflictingContentLengthThrows() { + var fields = List.of( + hf(":status", "200"), + hf("content-length", "100"), + hf("content-length", "200")); + var ex = assertThrows(H2Exception.class, + () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); + + assertTrue(ex.getMessage().contains("Multiple Content-Length")); + } + + @Test + void duplicateIdenticalContentLengthAllowed() throws IOException { + var fields = List.of( + hf(":status", "200"), + hf("content-length", "100"), + hf("content-length", "100")); + var result = H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false); + + assertEquals(100, result.contentLength()); + } + + @Test + void noContentLengthReturnsMinusOne() throws IOException { + var fields = List.of(hf(":status", "200")); + var result = H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false); + + assertEquals(-1, result.contentLength()); + } + + @Test + void validTrailers() throws IOException { + var fields = List.of( + hf("x-checksum", "abc123"), + hf("x-request-id", "req-456")); + + var trailers = H2ResponseHeaderProcessor.processTrailers(fields, 1); + + assertEquals("abc123", trailers.firstValue("x-checksum")); + assertEquals("req-456", trailers.firstValue("x-request-id")); + } + + @Test + void trailerWithPseudoHeaderThrows() { + var fields = List.of(hf(":status", "200")); + var ex = assertThrows(H2Exception.class, + () -> H2ResponseHeaderProcessor.processTrailers(fields, 1)); + + assertTrue(ex.getMessage().contains("Trailer contains pseudo-header")); + } + + @Test + void emptyTrailersAllowed() throws IOException { + var trailers = H2ResponseHeaderProcessor.processTrailers(List.of(), 1); + + assertTrue(trailers.map().isEmpty()); + } + + @Test + void contentLengthMatchPasses() { + Assertions.assertDoesNotThrow(() -> { + H2ResponseHeaderProcessor.validateContentLength(100, 100, 1); + }); + } + + @Test + void contentLengthMismatchThrows() { + var ex = assertThrows(H2Exception.class, + () -> H2ResponseHeaderProcessor.validateContentLength(100, 50, 1)); + + assertTrue(ex.getMessage().contains("Content-Length mismatch")); + assertTrue(ex.getMessage().contains("expected 100")); + assertTrue(ex.getMessage().contains("received 50")); + } + + @Test + void noContentLengthSkipsValidation() { + Assertions.assertDoesNotThrow(() -> { + H2ResponseHeaderProcessor.validateContentLength(-1, 999, 1); + }); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/StreamRegistryTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/StreamRegistryTest.java new file mode 100644 index 000000000..c98c487b6 --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/StreamRegistryTest.java @@ -0,0 +1,188 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class StreamRegistryTest { + + private StreamRegistry registry; + private H2Muxer muxer; + + @BeforeEach + void setUp() { + registry = new StreamRegistry(); + // Create a muxer with minimal dependencies + H2Muxer.ConnectionCallback callback = new H2Muxer.ConnectionCallback() { + @Override + public boolean isAcceptingStreams() { + return true; + } + + @Override + public int getRemoteMaxHeaderListSize() { + return Integer.MAX_VALUE; + } + }; + H2FrameCodec codec = new H2FrameCodec(null, null, 16384); + muxer = new H2Muxer(callback, codec, 4096, "test-muxer", 65535); + } + + @AfterEach + void tearDown() { + if (muxer != null) { + muxer.shutdownNow(); + } + } + + private H2Exchange exchange(int streamId) { + H2Exchange ex = new H2Exchange(muxer, null, 0, 0, 65535); + ex.setStreamId(streamId); + return ex; + } + + @Test + void putAndGet() { + var ex = exchange(1); + registry.put(1, ex); + + assertSame(ex, registry.get(1)); + } + + @Test + void getReturnsNullForMissing() { + assertNull(registry.get(1)); + } + + @Test + void removeFromFastPath() { + var ex = exchange(1); + registry.put(1, ex); + + assertTrue(registry.remove(1)); + assertNull(registry.get(1)); + } + + @Test + void removeReturnsFalseForMissing() { + assertFalse(registry.remove(1)); + } + + @Test + void multipleStreams() { + var ex1 = exchange(1); + var ex3 = exchange(3); + var ex5 = exchange(5); + + registry.put(1, ex1); + registry.put(3, ex3); + registry.put(5, ex5); + + assertSame(ex1, registry.get(1)); + assertSame(ex3, registry.get(3)); + assertSame(ex5, registry.get(5)); + } + + @Test + void spilloverOnCollision() { + // Stream IDs that map to the same slot (4096 slots, so IDs 1 and 1 + 4096*2 = 8193 collide) + int id1 = 1; + int id2 = 1 + 4096 * 2; // 8193 + + var ex1 = exchange(id1); + var ex2 = exchange(id2); + + registry.put(id1, ex1); + registry.put(id2, ex2); // Should spill over + + assertSame(ex1, registry.get(id1)); + assertSame(ex2, registry.get(id2)); + } + + @Test + void removeFromSpillover() { + int id1 = 1; + int id2 = 1 + 4096 * 2; + + var ex1 = exchange(id1); + var ex2 = exchange(id2); + + registry.put(id1, ex1); + registry.put(id2, ex2); + + assertTrue(registry.remove(id2)); // Remove from spillover + assertNull(registry.get(id2)); + assertSame(ex1, registry.get(id1)); // Original still there + } + + @Test + void forEach() { + var ex1 = exchange(1); + var ex3 = exchange(3); + + registry.put(1, ex1); + registry.put(3, ex3); + + List seen = new ArrayList<>(); + registry.forEach(seen, (ex, list) -> list.add(ex.getStreamId())); + + assertEquals(2, seen.size()); + assertTrue(seen.contains(1)); + assertTrue(seen.contains(3)); + } + + @Test + void forEachIncludesSpillover() { + int id1 = 1; + int id2 = 1 + 4096 * 2; + + registry.put(id1, exchange(id1)); + registry.put(id2, exchange(id2)); + + AtomicInteger count = new AtomicInteger(); + registry.forEach(null, (ex, ctx) -> count.incrementAndGet()); + + assertEquals(2, count.get()); + } + + @Test + void forEachMatching() { + registry.put(1, exchange(1)); + registry.put(3, exchange(3)); + registry.put(5, exchange(5)); + + List matched = new ArrayList<>(); + registry.forEachMatching(id -> id > 2, ex -> matched.add(ex.getStreamId())); + + assertEquals(2, matched.size()); + assertTrue(matched.contains(3)); + assertTrue(matched.contains(5)); + } + + @Test + void clearAndClose() { + registry.put(1, exchange(1)); + registry.put(3, exchange(3)); + + int id2 = 1 + 4096 * 2; // Collides with slot for ID 1 + registry.put(id2, exchange(id2)); // spillover + + AtomicInteger closed = new AtomicInteger(); + registry.clearAndClose(ex -> closed.incrementAndGet()); + + assertEquals(3, closed.get()); + assertNull(registry.get(1)); + assertNull(registry.get(3)); + assertNull(registry.get(id2)); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HuffmanTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HuffmanTest.java new file mode 100644 index 000000000..29db5f97c --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HuffmanTest.java @@ -0,0 +1,64 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2.hpack; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +/** + * Minimal API surface tests for Huffman. Comprehensive coverage is in HpackTestSuiteTest. + */ +class HuffmanTest { + + @Test + void roundTrip() throws IOException { + String input = "hello"; + byte[] inputBytes = input.getBytes(StandardCharsets.ISO_8859_1); + + var out = new ByteArrayOutputStream(); + Huffman.encode(inputBytes, 0, inputBytes.length, out); + byte[] encoded = out.toByteArray(); + + String decoded = Huffman.decode(encoded, 0, encoded.length); + assertEquals(input, decoded); + } + + @Test + void encodedLengthMatchesActual() throws IOException { + byte[] input = "www.example.com".getBytes(StandardCharsets.ISO_8859_1); + int predicted = Huffman.encodedLength(input, 0, input.length); + + var out = new ByteArrayOutputStream(); + Huffman.encode(input, 0, input.length, out); + + assertEquals(predicted, out.size()); + } + + @Test + void emptyInput() throws IOException { + byte[] empty = new byte[0]; + + var out = new ByteArrayOutputStream(); + Huffman.encode(empty, 0, 0, out); + assertEquals(0, out.size()); + + assertEquals("", Huffman.decode(empty, 0, 0)); + assertEquals(0, Huffman.encodedLength(empty, 0, 0)); + } + + @Test + void incompleteEncodingThrows() { + // Single byte that starts a multi-byte sequence but doesn't complete it. + // '0' is 5 bits, needs padding but 0x00 has wrong padding. + byte[] incomplete = {(byte) 0x00}; + + assertThrows(IOException.class, () -> Huffman.decode(incomplete, 0, incomplete.length)); + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTableTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTableTest.java new file mode 100644 index 000000000..808b21c7c --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTableTest.java @@ -0,0 +1,117 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2.hpack; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.smithy.java.http.api.HeaderNames; + +class StaticTableTest { + + @Test + void sizeIs61() { + assertEquals(61, StaticTable.SIZE); + } + + @ParameterizedTest(name = "index {0}: {1}={2}") + @MethodSource("staticTableEntries") + void getReturnsCorrectEntry(int index, String expectedName, String expectedValue) { + HeaderField field = StaticTable.get(index); + assertEquals(expectedName, field.name()); + assertEquals(expectedValue, field.value()); + } + + static Stream staticTableEntries() { + return Stream.of( + Arguments.of(1, ":authority", ""), + Arguments.of(2, ":method", "GET"), + Arguments.of(3, ":method", "POST"), + Arguments.of(4, ":path", "/"), + Arguments.of(5, ":path", "/index.html"), + Arguments.of(6, ":scheme", "http"), + Arguments.of(7, ":scheme", "https"), + Arguments.of(8, ":status", "200"), + Arguments.of(9, ":status", "204"), + Arguments.of(14, ":status", "500"), + Arguments.of(16, "accept-encoding", "gzip, deflate"), + Arguments.of(28, "content-length", ""), + Arguments.of(31, "content-type", ""), + Arguments.of(38, "host", ""), + Arguments.of(61, "www-authenticate", "")); + } + + @ParameterizedTest(name = "findFullMatch({0}, {1}) = {2}") + @MethodSource("fullMatchCases") + void findFullMatch(String name, String value, int expectedIndex) { + assertEquals(expectedIndex, StaticTable.findFullMatch(name, value)); + } + + static Stream fullMatchCases() { + return Stream.of( + Arguments.of(":method", "GET", 2), + Arguments.of(":method", "POST", 3), + Arguments.of(":path", "/", 4), + Arguments.of(":path", "/index.html", 5), + Arguments.of(":scheme", "http", 6), + Arguments.of(":scheme", "https", 7), + Arguments.of(":status", "200", 8), + Arguments.of(":status", "404", 13), + Arguments.of("accept-encoding", "gzip, deflate", 16), + // No match cases + Arguments.of(":method", "PUT", -1), + Arguments.of(":status", "201", -1), + Arguments.of("x-custom", "value", -1), + Arguments.of("content-type", "application/json", -1)); + } + + @ParameterizedTest(name = "findNameMatch({0}) = {1}") + @MethodSource("nameMatchCases") + void findNameMatch(String name, int expectedIndex) { + assertEquals(expectedIndex, StaticTable.findNameMatch(name)); + } + + static Stream nameMatchCases() { + return Stream.of( + Arguments.of(":authority", 1), + Arguments.of(":method", 2), + Arguments.of(":path", 4), + Arguments.of(":scheme", 6), + Arguments.of(":status", 8), + Arguments.of("accept", 19), + Arguments.of("content-length", 28), + Arguments.of("content-type", 31), + Arguments.of("host", 38), + Arguments.of("www-authenticate", 61), + // No match + Arguments.of("x-custom", -1), + Arguments.of("x-request-id", -1)); + } + + @Test + void findFullMatchWithHeaderNamesConstant() { + // Using HeaderNames constant should enable pointer comparison optimization + assertEquals(2, StaticTable.findFullMatch(HeaderNames.PSEUDO_METHOD, "GET")); + assertEquals(8, StaticTable.findFullMatch(HeaderNames.PSEUDO_STATUS, "200")); + } + + @Test + void findNameMatchWithHeaderNamesConstant() { + assertEquals(1, StaticTable.findNameMatch(HeaderNames.PSEUDO_AUTHORITY)); + assertEquals(31, StaticTable.findNameMatch(HeaderNames.CONTENT_TYPE)); + } + + @Test + void veryLongNameReturnsNoMatch() { + String longName = "x".repeat(100); + assertEquals(-1, StaticTable.findFullMatch(longName, "value")); + assertEquals(-1, StaticTable.findNameMatch(longName)); + } +} From 430aca3f7c53d0166698cb76457ffb1cc47f3062 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 12 Feb 2026 14:20:03 -0600 Subject: [PATCH 56/60] Move hpack to own package, improve conn balancing --- http/http-client/build.gradle.kts | 1 + .../java/http/client/H2cScalingBenchmark.java | 23 +- .../connection/H2ConnectionManager.java | 145 +++----- .../client/connection/H2LoadBalancer.java | 50 +++ .../client/connection/HttpConnectionPool.java | 1 + .../connection/HttpConnectionPoolBuilder.java | 16 + .../connection/WatermarkLoadBalancer.java | 71 ++++ .../java/http/client/h2/H2Connection.java | 15 +- .../java/http/client/h2/H2Exchange.java | 15 +- .../smithy/java/http/client/h2/H2Muxer.java | 2 +- .../client/h2/H2RequestHeaderEncoder.java | 2 +- .../client/h2/H2ResponseHeaderProcessor.java | 70 +--- .../http/client/h2/hpack/HeaderField.java | 17 - .../http/client/h2/hpack/HpackDecoder.java | 326 ------------------ .../http/client/h2/hpack/StaticTable.java | 202 ----------- .../h2/H2ResponseHeaderProcessorTest.java | 120 ++++--- http/http-hpack/build.gradle.kts | 15 + .../smithy/java/http}/hpack/DynamicTable.java | 130 ++++--- .../smithy/java/http/hpack/HpackDecoder.java | 277 +++++++++++++++ .../smithy/java/http}/hpack/HpackEncoder.java | 71 ++-- .../smithy/java/http}/hpack/Huffman.java | 78 +++-- .../smithy/java/http/hpack/StaticTable.java | 210 +++++++++++ .../java/http}/hpack/DynamicTableTest.java | 16 +- .../java/http}/hpack/HpackDecoderTest.java | 49 +-- .../java/http}/hpack/HpackEncoderTest.java | 86 ++--- .../java/http}/hpack/HpackTestSuiteTest.java | 15 +- .../smithy/java/http}/hpack/HuffmanTest.java | 2 +- .../java/http}/hpack/StaticTableTest.java | 10 +- .../test/resources/hpack-test-case/LICENSE | 0 .../resources/hpack-test-case/story_00.json | 0 .../resources/hpack-test-case/story_01.json | 0 .../resources/hpack-test-case/story_02.json | 0 .../resources/hpack-test-case/story_03.json | 0 .../resources/hpack-test-case/story_04.json | 0 .../resources/hpack-test-case/story_05.json | 0 .../resources/hpack-test-case/story_06.json | 0 .../resources/hpack-test-case/story_07.json | 0 .../resources/hpack-test-case/story_08.json | 0 .../resources/hpack-test-case/story_09.json | 0 .../resources/hpack-test-case/story_10.json | 0 .../resources/hpack-test-case/story_11.json | 0 .../resources/hpack-test-case/story_12.json | 0 .../resources/hpack-test-case/story_13.json | 0 .../resources/hpack-test-case/story_14.json | 0 .../resources/hpack-test-case/story_15.json | 0 .../resources/hpack-test-case/story_16.json | 0 .../resources/hpack-test-case/story_17.json | 0 .../resources/hpack-test-case/story_18.json | 0 .../resources/hpack-test-case/story_19.json | 0 .../resources/hpack-test-case/story_20.json | 0 .../resources/hpack-test-case/story_21.json | 0 .../resources/hpack-test-case/story_22.json | 0 .../resources/hpack-test-case/story_23.json | 0 .../resources/hpack-test-case/story_24.json | 0 .../resources/hpack-test-case/story_25.json | 0 .../resources/hpack-test-case/story_26.json | 0 .../resources/hpack-test-case/story_27.json | 0 .../resources/hpack-test-case/story_28.json | 0 .../resources/hpack-test-case/story_29.json | 0 .../resources/hpack-test-case/story_30.json | 0 .../resources/hpack-test-case/story_31.json | 0 settings.gradle.kts | 1 + 62 files changed, 1046 insertions(+), 990 deletions(-) create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2LoadBalancer.java create mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/WatermarkLoadBalancer.java delete mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HeaderField.java delete mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java delete mode 100644 http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTable.java create mode 100644 http/http-hpack/build.gradle.kts rename http/{http-client/src/main/java/software/amazon/smithy/java/http/client/h2 => http-hpack/src/main/java/software/amazon/smithy/java/http}/hpack/DynamicTable.java (51%) create mode 100644 http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/HpackDecoder.java rename http/{http-client/src/main/java/software/amazon/smithy/java/http/client/h2 => http-hpack/src/main/java/software/amazon/smithy/java/http}/hpack/HpackEncoder.java (75%) rename http/{http-client/src/main/java/software/amazon/smithy/java/http/client/h2 => http-hpack/src/main/java/software/amazon/smithy/java/http}/hpack/Huffman.java (89%) create mode 100644 http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/StaticTable.java rename http/{http-client/src/test/java/software/amazon/smithy/java/http/client/h2 => http-hpack/src/test/java/software/amazon/smithy/java/http}/hpack/DynamicTableTest.java (91%) rename http/{http-client/src/test/java/software/amazon/smithy/java/http/client/h2 => http-hpack/src/test/java/software/amazon/smithy/java/http}/hpack/HpackDecoderTest.java (75%) rename http/{http-client/src/test/java/software/amazon/smithy/java/http/client/h2 => http-hpack/src/test/java/software/amazon/smithy/java/http}/hpack/HpackEncoderTest.java (72%) rename http/{http-client/src/test/java/software/amazon/smithy/java/http/client/h2 => http-hpack/src/test/java/software/amazon/smithy/java/http}/hpack/HpackTestSuiteTest.java (90%) rename http/{http-client/src/test/java/software/amazon/smithy/java/http/client/h2 => http-hpack/src/test/java/software/amazon/smithy/java/http}/hpack/HuffmanTest.java (97%) rename http/{http-client/src/test/java/software/amazon/smithy/java/http/client/h2 => http-hpack/src/test/java/software/amazon/smithy/java/http}/hpack/StaticTableTest.java (92%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/LICENSE (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_00.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_01.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_02.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_03.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_04.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_05.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_06.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_07.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_08.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_09.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_10.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_11.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_12.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_13.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_14.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_15.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_16.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_17.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_18.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_19.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_20.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_21.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_22.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_23.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_24.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_25.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_26.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_27.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_28.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_29.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_30.json (100%) rename http/{http-client => http-hpack}/src/test/resources/hpack-test-case/story_31.json (100%) diff --git a/http/http-client/build.gradle.kts b/http/http-client/build.gradle.kts index 21af5d4e3..a2846ccd0 100644 --- a/http/http-client/build.gradle.kts +++ b/http/http-client/build.gradle.kts @@ -22,6 +22,7 @@ val jmhServerImplementation by configurations.getting dependencies { api(project(":http:http-api")) + api(project(":http:http-hpack")) api(project(":context")) api(project(":logging")) diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java index ce1d941c8..f6d37a621 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java @@ -55,6 +55,7 @@ import org.openjdk.jmh.annotations.Warmup; import software.amazon.smithy.java.http.api.HttpRequest; import software.amazon.smithy.java.http.client.connection.ConnectionPoolListener; +import software.amazon.smithy.java.http.client.connection.H2LoadBalancer; import software.amazon.smithy.java.http.client.connection.HttpConnection; import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; @@ -92,6 +93,9 @@ public class H2cScalingBenchmark { @Param({"100"}) private int streamsPerConnection; + @Param({"1000"}) + private int totalRequests; + private HttpClient smithyClient; private Http2Client helidonClient; @@ -117,6 +121,7 @@ public void setupIteration() throws Exception { .maxConnectionsPerRoute(connections) .maxTotalConnections(connections) .h2StreamsPerConnection(streamsPerConnection) + .h2LoadBalancer(H2LoadBalancer.watermark(1, streamsPerConnection)) .h2InitialWindowSize(1024 * 1024) .maxIdleTime(Duration.ofMinutes(2)) .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) @@ -231,7 +236,7 @@ public void smithy(Counter counter) throws InterruptedException { var uri = URI.create(BenchmarkSupport.H2C_URL + "/get"); var request = HttpRequest.builder().uri(uri).method("GET").build(); - BenchmarkSupport.runBenchmark(concurrency, concurrency, (HttpRequest req) -> { + BenchmarkSupport.runBenchmark(concurrency, totalRequests, (HttpRequest req) -> { try (var res = smithyClient.send(req)) { res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); } @@ -243,7 +248,7 @@ public void smithy(Counter counter) throws InterruptedException { @Benchmark @Threads(1) public void helidon(Counter counter) throws InterruptedException { - BenchmarkSupport.runBenchmark(concurrency, concurrency, (Http2Client client) -> { + BenchmarkSupport.runBenchmark(concurrency, totalRequests, (Http2Client client) -> { try (HttpClientResponse response = client.get("/get").request()) { response.entity().consume(); } @@ -262,7 +267,7 @@ public void smithyPost(Counter counter) throws InterruptedException { .body(DataStream.ofBytes(BenchmarkSupport.POST_PAYLOAD)) .build(); - BenchmarkSupport.runBenchmark(concurrency, concurrency, (HttpRequest req) -> { + BenchmarkSupport.runBenchmark(concurrency, totalRequests, (HttpRequest req) -> { try (var res = smithyClient.send(req)) { res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); } @@ -281,7 +286,7 @@ public void smithyPutMb(Counter counter) throws InterruptedException { .body(DataStream.ofBytes(BenchmarkSupport.MB_PAYLOAD)) .build(); - BenchmarkSupport.runBenchmark(concurrency, concurrency, (HttpRequest req) -> { + BenchmarkSupport.runBenchmark(concurrency, totalRequests, (HttpRequest req) -> { try (var res = smithyClient.send(req)) { res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); } @@ -296,7 +301,7 @@ public void smithyGetMb(Counter counter) throws InterruptedException { var uri = URI.create(BenchmarkSupport.H2C_URL + "/getmb"); var request = HttpRequest.builder().uri(uri).method("GET").build(); - BenchmarkSupport.runBenchmark(concurrency, concurrency, (HttpRequest req) -> { + BenchmarkSupport.runBenchmark(concurrency, totalRequests, (HttpRequest req) -> { try (var res = smithyClient.send(req)) { res.body().asInputStream().transferTo(OutputStream.nullOutputStream()); } @@ -316,7 +321,7 @@ public void netty(Counter counter) throws Exception { var connectionIndex = new AtomicInteger(0); - BenchmarkSupport.runBenchmark(concurrency, concurrency, (DefaultHttp2Headers h) -> { + BenchmarkSupport.runBenchmark(concurrency, totalRequests, (DefaultHttp2Headers h) -> { var latch = new CountDownLatch(1); var error = new AtomicReference(); @@ -374,7 +379,7 @@ public void nettyGetMb(Counter counter) throws Exception { var connectionIndex = new AtomicInteger(0); - BenchmarkSupport.runBenchmark(concurrency, concurrency, (DefaultHttp2Headers h) -> { + BenchmarkSupport.runBenchmark(concurrency, totalRequests, (DefaultHttp2Headers h) -> { var latch = new CountDownLatch(1); var error = new AtomicReference(); @@ -440,7 +445,7 @@ public void nettyPost(Counter counter) throws Exception { var connectionIndex = new AtomicInteger(0); - BenchmarkSupport.runBenchmark(concurrency, concurrency, (DefaultHttp2Headers h) -> { + BenchmarkSupport.runBenchmark(concurrency, totalRequests, (DefaultHttp2Headers h) -> { var latch = new CountDownLatch(1); var error = new AtomicReference(); @@ -504,7 +509,7 @@ public void nettyPutMb(Counter counter) throws Exception { var connectionIndex = new AtomicInteger(0); - BenchmarkSupport.runBenchmark(concurrency, concurrency, (DefaultHttp2Headers h) -> { + BenchmarkSupport.runBenchmark(concurrency, totalRequests, (DefaultHttp2Headers h) -> { var latch = new CountDownLatch(1); var error = new AtomicReference(); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java index a94821835..d10e70f43 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java @@ -9,36 +9,26 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiConsumer; import software.amazon.smithy.java.http.client.h2.H2Connection; +import software.amazon.smithy.java.logging.InternalLogger; /** * Manages HTTP/2 connections with adaptive load balancing. * *

        Load Balancing Strategy

        - *

        Uses a two-tier "watermark" strategy to distribute streams across connections: - * - *

          - *
        1. Green Zone (streams < soft limit): Uses atomic round-robin to distribute - * requests evenly. Each caller atomically increments an index to get a unique starting - * position, ensuring fair distribution even under high concurrency.
        2. - *
        3. Expansion: When all connections exceed the soft limit, creates a new connection - * (if under the maximum). This prevents overloading a single TCP connection.
        4. - *
        5. Red Zone (at max connections): Uses least-loaded selection to find the - * connection with the fewest active streams, up to the hard limit.
        6. - *
        7. Saturation: When all connections are at the hard limit, callers wait - * for capacity with a configurable timeout.
        8. - *
        + *

        Uses {@link H2LoadBalancer}, which by default uses a high-watermark strategy. * *

        Threading

        *

        Uses per-route state with a volatile connection array for lock-free reads in the * common case. Connection creation and removal synchronize on the per-route state object. - * The round-robin index uses {@link AtomicInteger} for visibility and atomicity across - * thousands of concurrent virtual threads. */ final class H2ConnectionManager { + private static final InternalLogger LOGGER = InternalLogger.getLogger(H2ConnectionManager.class); + /** * Per-route connection state. */ @@ -49,28 +39,24 @@ private static final class RouteState { /** Connections currently being created (prevents over-creation). Guarded by lock. */ int pendingCreations = 0; - /** Round-robin index for connection selection. Atomic for concurrent access. */ - final AtomicInteger nextIndex = new AtomicInteger(0); + /** Scratch buffer for active stream counts, guarded by lock. */ + int[] activeStreamsBuf = new int[4]; /** Lock for state modifications. ReentrantLock avoids VT pinning unlike synchronized. */ - final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(); - final java.util.concurrent.locks.Condition available = lock.newCondition(); + final ReentrantLock lock = new ReentrantLock(); + final Condition available = lock.newCondition(); } private static final H2Connection[] EMPTY = new H2Connection[0]; // Soft limit as a fraction of streamsPerConnection. When all connections exceed this threshold, // we try to create a new connection (if under max). - // This prevents overloading a single TCP connection even when the server allows many streams. - private static final int SOFT_LIMIT_DIVISOR = 4; // Create new connection at 25% utilization - // Floor ensures we don't create excessive connections for servers with low max_concurrent_streams. - // 25 streams is a reasonable threshold - below this, a single connection handles load well; - // above this, spreading load across connections reduces head-of-line blocking. - private static final int SOFT_LIMIT_FLOOR = 25; + private static final int DEFAULT_SOFT_LIMIT_DIVISOR = 4; + private static final int DEFAULT_SOFT_LIMIT_FLOOR = 25; private final ConcurrentHashMap routes = new ConcurrentHashMap<>(); - private final int streamsPerConnection; // Hard limit from server - private final int softConcurrencyLimit; // Soft limit for load balancing + private final int streamsPerConnection; + private final H2LoadBalancer loadBalancer; private final long acquireTimeoutMs; private final List listeners; private final ConnectionFactory connectionFactory; @@ -82,15 +68,23 @@ interface ConnectionFactory { H2ConnectionManager( int streamsPerConnection, + H2LoadBalancer loadBalancer, long acquireTimeoutMs, List listeners, ConnectionFactory connectionFactory ) { this.streamsPerConnection = streamsPerConnection; - this.softConcurrencyLimit = Math.max(SOFT_LIMIT_FLOOR, streamsPerConnection / SOFT_LIMIT_DIVISOR); this.acquireTimeoutMs = acquireTimeoutMs; this.listeners = listeners; this.connectionFactory = connectionFactory; + + if (loadBalancer != null) { + this.loadBalancer = loadBalancer; + } else { + this.loadBalancer = H2LoadBalancer.watermark( + Math.max(DEFAULT_SOFT_LIMIT_FLOOR, streamsPerConnection / DEFAULT_SOFT_LIMIT_DIVISOR), + streamsPerConnection); + } } private RouteState stateFor(Route route) { @@ -120,29 +114,32 @@ H2Connection acquire(Route route, int maxConnectionsForRoute) throws IOException try { while (true) { H2Connection[] snapshot = state.conns; - int totalConns = snapshot.length + state.pendingCreations; + int connCount = snapshot.length; + int totalConns = connCount + state.pendingCreations; - // Green zone: round-robin among connections under soft limit - H2Connection conn = tryAcquireRoundRobin(snapshot, state, softConcurrencyLimit); - if (conn != null) { - notifyAcquire(conn, true); - return conn; + // Build active stream counts for the load balancer + if (state.activeStreamsBuf.length < connCount) { + state.activeStreamsBuf = new int[connCount]; } + for (int i = 0; i < connCount; i++) { + state.activeStreamsBuf[i] = snapshot[i].getActiveStreamCountIfAccepting(); + } + + boolean canExpand = totalConns < maxConnectionsForRoute; + int selected = loadBalancer.select(state.activeStreamsBuf, connCount, + canExpand ? maxConnectionsForRoute : connCount); - // Expansion: all connections above soft limit, create new if allowed - if (totalConns < maxConnectionsForRoute) { + if (selected >= 0) { + notifyAcquire(snapshot[selected], true); + return snapshot[selected]; + } else if (selected == H2LoadBalancer.CREATE_NEW && canExpand) { state.pendingCreations++; break; } - // Red zone: at max connections, use least-loaded up to hard limit - conn = tryAcquireLeastLoaded(snapshot, streamsPerConnection); - if (conn != null) { - notifyAcquire(conn, true); - return conn; - } - - // Saturation: all connections at hard limit, wait for capacity + // Saturated: wait for capacity + LOGGER.debug("All {} connections saturated for route {}, waiting for capacity " + + "(canExpand={}, selected={})", connCount, route, canExpand, selected); long remainingNanos = deadlineNanos - System.nanoTime(); if (remainingNanos <= 0) { throw new IOException("Acquire timeout: no connection available after " @@ -197,64 +194,6 @@ private H2Connection createNewH2Connection(Route route, RouteState state) throws return newConn; } - /** - * Round-robin selection: find a connection under the limit, starting from a unique index. - * - *

        Each caller atomically claims a starting index, then scans connections from that - * position. This ensures even distribution under high concurrency, each gets a different starting point rather - * than all hammering connection[0]. - */ - private H2Connection tryAcquireRoundRobin(H2Connection[] conns, RouteState state, int limit) { - int n = conns.length; - if (n == 0) { - return null; - } - - // Mask with MAX_VALUE handles overflow when counter wraps to negative. - int start = (state.nextIndex.getAndIncrement() & Integer.MAX_VALUE) % n; - - for (int i = 0; i < n; i++) { - int idx = (start + i) % n; - H2Connection conn = conns[idx]; - if (conn == null) { - continue; - } - // getActiveStreamCountIfAccepting(): returns active stream count, or <0 if not accepting new streams - int active = conn.getActiveStreamCountIfAccepting(); - if (active >= 0 && active < limit) { - return conn; - } - } - return null; - } - - /** - * Least-loaded selection: find the connection with the fewest active streams. - * - *

        Scans all connections to find the best candidate. Used in the red zone when all connections exceed the - * soft limit and we need to balance the load. - */ - private H2Connection tryAcquireLeastLoaded(H2Connection[] conns, int limit) { - H2Connection best = null; - int bestActive = Integer.MAX_VALUE; - - for (H2Connection conn : conns) { - if (conn == null) { - continue; - } - int active = conn.getActiveStreamCountIfAccepting(); - if (active >= 0 && active < limit && active < bestActive) { - best = conn; - bestActive = active; - if (active == 0) { - break; - } - } - } - - return best; - } - /** * Unregister a connection from the route. */ diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2LoadBalancer.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2LoadBalancer.java new file mode 100644 index 000000000..f887f4fa3 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2LoadBalancer.java @@ -0,0 +1,50 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +/** + * Strategy for selecting hat HTTP/2 connection to use or whether to create a new one. + * + *

        The strategy receives an array of active stream counts (one per connection) and returns the index of the + * connection to use, or -1 to signal that a new connection should be created. + */ +@FunctionalInterface +public interface H2LoadBalancer { + + /** Return value indicating a new connection should be created. */ + int CREATE_NEW = -1; + + /** Return value indicating all connections are saturated. */ + int SATURATED = -2; + + /** + * Select a connection index or signal new connection creation. + * + *

        When {@code maxConnections == connectionCount}, the balancer must not return {@link #CREATE_NEW} + * (no room to expand). Return a valid index or {@link #SATURATED} instead. + * + * @param activeStreams active stream count per connection; a value of -1 means + * the connection is not accepting new streams + * @param connectionCount number of valid entries in activeStreams + * @param maxConnections maximum connections allowed; equals connectionCount when expansion is not possible + * @return index into activeStreams to use, {@link #CREATE_NEW}, or {@link #SATURATED} + */ + int select(int[] activeStreams, int connectionCount, int maxConnections); + + /** + * Create a watermark-based load balancer. + * + *

        Uses a two-tier strategy: prefers connections under the soft limit via round-robin, + * expands when all exceed it, and falls back to least-loaded up to the hard limit. + * + * @param softLimit expand to a new connection when all connections have at least this many streams + * @param hardLimit maximum streams per connection (never exceed this) + * @return a watermark load balancer + */ + static H2LoadBalancer watermark(int softLimit, int hardLimit) { + return new WatermarkLoadBalancer(softLimit, hardLimit); + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java index b726c6ef4..458222206 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPool.java @@ -188,6 +188,7 @@ public final class HttpConnectionPool implements ConnectionPool { this.connectionPermits = new Semaphore(builder.maxTotalConnections, false); this.listeners = List.copyOf(builder.listeners); this.h2Manager = new H2ConnectionManager(builder.h2StreamsPerConnection, + builder.h2LoadBalancer, this.acquireTimeoutMs, listeners, this::onNewH2Connection); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java index 08dacb5da..486a19717 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/HttpConnectionPoolBuilder.java @@ -24,6 +24,7 @@ public final class HttpConnectionPoolBuilder { int maxTotalConnections = 256; int maxConnectionsPerRoute = 20; int h2StreamsPerConnection = 100; + H2LoadBalancer h2LoadBalancer = null; int h2InitialWindowSize = 65535; // RFC 9113 default int h2MaxFrameSize = 16384; // RFC 9113 default int h2BufferSize = 256 * 1024; // 256KB default @@ -493,6 +494,21 @@ public HttpConnectionPoolBuilder h2StreamsPerConnection(int streams) { return this; } + /** + * Set the HTTP/2 load balancer strategy for distributing streams across connections. + * + *

        Default: watermark strategy at 25% of {@code h2StreamsPerConnection} (floor 25). + * Use {@link H2LoadBalancer#watermark(int, int)} to create a watermark balancer with + * custom soft/hard limits, or provide a custom implementation. + * + * @param loadBalancer the load balancer to use + * @return this builder + */ + public HttpConnectionPoolBuilder h2LoadBalancer(H2LoadBalancer loadBalancer) { + this.h2LoadBalancer = loadBalancer; + return this; + } + /** * Set HTTP/2 I/O buffer size (default: 256KB). * diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/WatermarkLoadBalancer.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/WatermarkLoadBalancer.java new file mode 100644 index 000000000..490eb3c11 --- /dev/null +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/WatermarkLoadBalancer.java @@ -0,0 +1,71 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.connection; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Watermark-based load balancer for HTTP/2 connections. + * + *

        Green zone (under soft limit): round-robin. + * Expansion: all above soft limit and under max connections → {@link H2LoadBalancer#CREATE_NEW}. + * Red zone (at max connections): least-loaded under hard limit. + * Saturated: returns {@link H2LoadBalancer#SATURATED}. + */ +final class WatermarkLoadBalancer implements H2LoadBalancer { + + private final int softLimit; + private final int hardLimit; + private final AtomicInteger nextIndex = new AtomicInteger(0); + + WatermarkLoadBalancer(int softLimit, int hardLimit) { + if (softLimit > hardLimit) { + throw new IllegalArgumentException("Soft limit must not exceed hard limit"); + } + + this.softLimit = softLimit; + this.hardLimit = hardLimit; + } + + @Override + public int select(int[] activeStreams, int connectionCount, int maxConnections) { + // Green zone: round-robin among connections under soft limit + if (connectionCount > 0) { + int start = (nextIndex.getAndIncrement() & Integer.MAX_VALUE) % connectionCount; + for (int i = 0; i < connectionCount; i++) { + int idx = start + i; + if (idx >= connectionCount) { + idx -= connectionCount; + } + int active = activeStreams[idx]; + if (active >= 0 && active < softLimit) { + return idx; + } + } + } + + // Expansion: all above soft limit, create new if allowed + if (connectionCount < maxConnections) { + return H2LoadBalancer.CREATE_NEW; + } + + // Red zone: least-loaded under hard limit + int bestIdx = H2LoadBalancer.SATURATED; + int bestActive = Integer.MAX_VALUE; + for (int i = 0; i < connectionCount; i++) { + int active = activeStreams[i]; + if (active >= 0 && active < hardLimit && active < bestActive) { + bestIdx = i; + bestActive = active; + if (active == 0) { + break; + } + } + } + + return bestIdx; + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java index 2fc92bf8e..e7407566e 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Connection.java @@ -51,8 +51,7 @@ import software.amazon.smithy.java.http.client.UnsyncBufferedOutputStream; import software.amazon.smithy.java.http.client.connection.HttpConnection; import software.amazon.smithy.java.http.client.connection.Route; -import software.amazon.smithy.java.http.client.h2.hpack.HeaderField; -import software.amazon.smithy.java.http.client.h2.hpack.HpackDecoder; +import software.amazon.smithy.java.http.hpack.HpackDecoder; import software.amazon.smithy.java.logging.InternalLogger; /** @@ -384,7 +383,7 @@ private void dispatchStreamFrame(H2Exchange exchange, int type, int streamId, by // Note: WINDOW_UPDATE and RST_STREAM are handled in handleNonDataFrame fast path switch (type) { case FRAME_TYPE_HEADERS -> { - List decoded; + List decoded; if (payload != null && length > 0) { decoded = decodeHeaders(payload, length); } else { @@ -751,16 +750,16 @@ public void close() throws IOException { } // Called only from reader thread - no synchronization needed - List decodeHeaders(byte[] headerBlock, int length) throws IOException { + List decodeHeaders(byte[] headerBlock, int length) throws IOException { int maxHeaderListSize = H2Constants.DEFAULT_MAX_HEADER_LIST_SIZE; if (length > maxHeaderListSize) { throw new H2Exception(ERROR_ENHANCE_YOUR_CALM, "Header block size " + length + " exceeds limit " + maxHeaderListSize); } - List headers; + List headers; try { - headers = hpackDecoder.decodeBlock(headerBlock, 0, length); + headers = hpackDecoder.decode(headerBlock, 0, length); } catch (IOException e) { active = false; LOGGER.debug("HPACK decoding failed for {}: {}", route, e.getMessage()); @@ -772,8 +771,8 @@ List decodeHeaders(byte[] headerBlock, int length) throws IOExcepti } int decodedSize = 0; - for (HeaderField field : headers) { - decodedSize += field.name().length() + field.value().length() + 32; + for (int i = 0; i < headers.size(); i += 2) { + decodedSize += headers.get(i).length() + headers.get(i + 1).length() + 32; if (decodedSize > maxHeaderListSize) { throw new H2Exception(ERROR_ENHANCE_YOUR_CALM, "Decoded header list size exceeds limit " + maxHeaderListSize); diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java index 659ac04f1..49be477fb 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Exchange.java @@ -35,7 +35,6 @@ import software.amazon.smithy.java.http.client.DelegatedClosingInputStream; import software.amazon.smithy.java.http.client.DelegatedClosingOutputStream; import software.amazon.smithy.java.http.client.HttpExchange; -import software.amazon.smithy.java.http.client.h2.hpack.HeaderField; /** * HTTP/2 exchange implementation for a single stream with multiplexing support. @@ -84,7 +83,7 @@ public final class H2Exchange implements HttpExchange { private final H2StreamState state = new H2StreamState(); // Pending headers from reader thread (protected by dataLock) - private List pendingHeaders; + private List pendingHeaders; private boolean pendingHeadersEndStream; // === Data chunk queue === @@ -350,7 +349,7 @@ void returnBuffer(byte[] buffer) { * @param fields the decoded header fields * @param endStream whether END_STREAM flag was set */ - void deliverHeaders(List fields, boolean endStream) { + void deliverHeaders(List fields, boolean endStream) { dataLock.lock(); try { pendingHeaders = fields; @@ -498,7 +497,7 @@ int drainChunks(DataChunk[] dest, int maxChunks) throws IOException { while (dataQueue.isEmpty() && state.getReadState() == RS_READING) { // Check for pending trailers if (pendingHeaders != null) { - List fields = pendingHeaders; + List fields = pendingHeaders; boolean endStream = pendingHeadersEndStream; pendingHeaders = null; handleHeadersEvent(fields, endStream); @@ -766,7 +765,7 @@ private void readResponseHeaders() throws IOException { dataLock.lock(); try { if (pendingHeaders != null) { - List fields = pendingHeaders; + List fields = pendingHeaders; boolean endStream = pendingHeadersEndStream; pendingHeaders = null; // Consume the headers @@ -787,7 +786,7 @@ private void readResponseHeaders() throws IOException { * @param fields the decoded header fields * @param isEndStream whether END_STREAM flag was set */ - private void handleHeadersEvent(List fields, boolean isEndStream) throws IOException { + private void handleHeadersEvent(List fields, boolean isEndStream) throws IOException { int ss = state.getStreamState(); // Allow processing headers if the stream is CLOSED but closed cleanly (RS_DONE) @@ -834,7 +833,7 @@ private void handleHeadersEvent(List fields, boolean isEndStream) t * @param fields the decoded header fields * @param isEndStream whether this HEADERS frame has END_STREAM flag */ - private void processResponseHeaders(List fields, boolean isEndStream) throws IOException { + private void processResponseHeaders(List fields, boolean isEndStream) throws IOException { H2ResponseHeaderProcessor.Result result = H2ResponseHeaderProcessor.processResponseHeaders(fields, streamId, isEndStream); @@ -859,7 +858,7 @@ private void processResponseHeaders(List fields, boolean isEndStrea * * @param fields the pre-decoded header fields */ - private void processTrailers(List fields) throws IOException { + private void processTrailers(List fields) throws IOException { this.trailerHeaders = H2ResponseHeaderProcessor.processTrailers(fields, streamId); } diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java index a11bb0262..b02b71e67 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2Muxer.java @@ -23,7 +23,7 @@ import java.util.function.BiConsumer; import software.amazon.smithy.java.http.api.HttpHeaders; import software.amazon.smithy.java.http.api.HttpRequest; -import software.amazon.smithy.java.http.client.h2.hpack.HpackEncoder; +import software.amazon.smithy.java.http.hpack.HpackEncoder; import software.amazon.smithy.java.io.ByteBufferOutputStream; /** diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2RequestHeaderEncoder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2RequestHeaderEncoder.java index 884774fa6..e210ddfc4 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2RequestHeaderEncoder.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2RequestHeaderEncoder.java @@ -14,7 +14,7 @@ import java.util.Set; import software.amazon.smithy.java.http.api.HttpHeaders; import software.amazon.smithy.java.http.api.HttpRequest; -import software.amazon.smithy.java.http.client.h2.hpack.HpackEncoder; +import software.amazon.smithy.java.http.hpack.HpackEncoder; import software.amazon.smithy.java.io.ByteBufferOutputStream; /** diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessor.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessor.java index 689a3804c..83a414359 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessor.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessor.java @@ -13,35 +13,21 @@ import java.util.Set; import software.amazon.smithy.java.http.api.HttpHeaders; import software.amazon.smithy.java.http.api.ModifiableHttpHeaders; -import software.amazon.smithy.java.http.client.h2.hpack.HeaderField; /** * Processes HTTP/2 response headers and trailers with RFC 9113 validation. * - *

        This class handles the validation and parsing of response HEADERS frames, - * including pseudo-header validation, Content-Length tracking, and trailer processing. - * - *

        RFC 9113 Compliance

        - *
          - *
        • Section 8.3: Pseudo-headers must appear before regular headers
        • - *
        • Section 8.3.2: Response must have exactly one :status pseudo-header
        • - *
        • Section 8.3: Request pseudo-headers not allowed in responses
        • - *
        • Section 8.1: Trailers must not contain pseudo-headers
        • - *
        • Section 8.1.1: Content-Length validation, 1xx responses must not have END_STREAM
        • - *
        + *

        Headers are passed as flat List<String>: [name0, value0, name1, value1, ...]. */ final class H2ResponseHeaderProcessor { - /** Request pseudo-headers (only allowed in requests, not responses). */ private static final Set REQUEST_PSEUDO_HEADERS = Set.of( ":method", ":scheme", ":authority", ":path"); - /** Result of processing response headers. */ record Result(HttpHeaders headers, int statusCode, long contentLength) { - /** Indicates an informational (1xx) response that should be skipped. */ static final Result INFORMATIONAL = new Result(null, -1, -1); boolean isInformational() { @@ -52,28 +38,22 @@ boolean isInformational() { private H2ResponseHeaderProcessor() {} /** - * Process response headers with full RFC 9113 validation. + * Process response headers. * - * @param fields the decoded header fields - * @param streamId the stream ID (for error messages) - * @param isEndStream whether END_STREAM flag was set - * @return the processing result, or {@link Result#INFORMATIONAL} for 1xx responses - * @throws H2Exception if headers violate RFC 9113 - * @throws IOException if headers are malformed + * @param fields flat list [name0, value0, name1, value1, ...] */ - static Result processResponseHeaders(List fields, int streamId, boolean isEndStream) + static Result processResponseHeaders(List fields, int streamId, boolean isEndStream) throws IOException { ModifiableHttpHeaders headers = HttpHeaders.ofModifiable(); int parsedStatusCode = -1; boolean seenRegularHeader = false; long contentLength = -1; - for (HeaderField field : fields) { - String name = field.name(); - String value = field.value(); + for (int i = 0; i < fields.size(); i += 2) { + String name = fields.get(i); + String value = fields.get(i + 1); if (name.startsWith(":")) { - // RFC 9113 Section 8.3: All pseudo-headers MUST appear before regular headers if (seenRegularHeader) { throw new H2Exception(ERROR_PROTOCOL_ERROR, streamId, @@ -81,7 +61,6 @@ static Result processResponseHeaders(List fields, int streamId, boo } if (name.equals(PSEUDO_STATUS)) { - // RFC 9113 Section 8.3.2: Response MUST have exactly one :status if (parsedStatusCode != -1) { throw new H2Exception(ERROR_PROTOCOL_ERROR, streamId, "Expected a single :status header"); } @@ -91,20 +70,16 @@ static Result processResponseHeaders(List fields, int streamId, boo throw new IOException("Invalid :status value: " + value); } } else if (REQUEST_PSEUDO_HEADERS.contains(name)) { - // RFC 9113 Section 8.3: Request pseudo-headers are NOT allowed in responses throw new H2Exception(ERROR_PROTOCOL_ERROR, streamId, "Request pseudo-header '" + name + "' in response"); } else { - // Unknown pseudo-header - RFC 9113 says endpoints MUST treat as malformed throw new H2Exception(ERROR_PROTOCOL_ERROR, streamId, "Unknown pseudo-header '" + name + "' in response"); } } else { - // Handle a regular header seenRegularHeader = true; - // Track Content-Length for validation per RFC 9113 Section 8.1.1 if ("content-length".equals(name)) { try { long parsedLength = Long.parseLong(value); @@ -113,10 +88,9 @@ static Result processResponseHeaders(List fields, int streamId, boo } contentLength = parsedLength; } catch (NumberFormatException e) { - throw new H2Exception(ERROR_PROTOCOL_ERROR, streamId, "Invalid Content-Length value: " + value); + throw new H2Exception(ERROR_PROTOCOL_ERROR, streamId, "Invalid Content-Length: " + value); } } - headers.addHeader(name, value); } } @@ -125,7 +99,6 @@ static Result processResponseHeaders(List fields, int streamId, boo throw new IOException("Response missing :status pseudo-header"); } - // Check if this is an informational (1xx) response, and skip and wait for final response if (parsedStatusCode >= 100 && parsedStatusCode < 200) { if (isEndStream) { throw new H2Exception(ERROR_PROTOCOL_ERROR, streamId, "1xx response must not have END_STREAM"); @@ -137,37 +110,22 @@ static Result processResponseHeaders(List fields, int streamId, boo } /** - * Process trailer headers per RFC 9113 Section 8.1. - * - *

        Trailers are HEADERS sent after DATA with END_STREAM. They MUST NOT - * contain pseudo-headers. + * Process trailer headers. * - * @param fields the decoded header fields - * @param streamId the stream ID (for error messages) - * @return the trailer headers - * @throws H2Exception if trailers contain pseudo-headers + * @param fields flat list [name0, value0, name1, value1, ...] */ - static HttpHeaders processTrailers(List fields, int streamId) throws IOException { + static HttpHeaders processTrailers(List fields, int streamId) throws IOException { ModifiableHttpHeaders trailers = HttpHeaders.ofModifiable(); - for (HeaderField field : fields) { - String name = field.name(); - // RFC 9113 Section 8.1: Trailers MUST NOT contain pseudo-headers + for (int i = 0; i < fields.size(); i += 2) { + String name = fields.get(i); if (name.startsWith(":")) { throw new H2Exception(ERROR_PROTOCOL_ERROR, streamId, "Trailer contains pseudo-header '" + name + "'"); } - trailers.addHeader(name, field.value()); + trailers.addHeader(name, fields.get(i + 1)); } return trailers; } - /** - * Validate Content-Length matches actual data received per RFC 9113 Section 8.1.1. - * - * @param expectedContentLength expected content length (-1 if not specified) - * @param receivedContentLength actual bytes received - * @param streamId the stream ID (for error messages) - * @throws H2Exception if there is a mismatch - */ static void validateContentLength(long expectedContentLength, long receivedContentLength, int streamId) throws IOException { if (expectedContentLength >= 0 && receivedContentLength != expectedContentLength) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HeaderField.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HeaderField.java deleted file mode 100644 index 24c774903..000000000 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HeaderField.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.h2.hpack; - -/** - * Decoded HPACK header field (name-value pair). - * - *

        This is the public type returned by HPACK decoding operations. - * Static table entries are pre-allocated and reused (zero allocation on lookup). - * - * @param name header field name (lowercase for HTTP/2) - * @param value header field value - */ -public record HeaderField(String name, String value) {} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java deleted file mode 100644 index fb894aa64..000000000 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoder.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.h2.hpack; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import software.amazon.smithy.java.http.api.HeaderNames; - -/** - * HPACK decoder for HTTP/2 header decompression (RFC 7541). - * - *

        This decoder decompresses HTTP headers from HPACK format back to name-value pairs. - * - *

        Thread safety: This class is NOT thread-safe. Each HTTP/2 connection should have its own decoder instance. - */ -public final class HpackDecoder { - - private final DynamicTable dynamicTable; - private final int maxHeaderListSize; - - /** Current position during decoding, reset at start of each decodeBlock call. */ - private int decodePos; - - /** - * Create a decoder with the given maximum dynamic table size. - * - * @param maxTableSize maximum dynamic table size in bytes - */ - public HpackDecoder(int maxTableSize) { - this(maxTableSize, 8192); - } - - /** - * Create a decoder with the given limits. - * - * @param maxTableSize maximum dynamic table size in bytes - * @param maxHeaderListSize maximum size of decoded header list - */ - public HpackDecoder(int maxTableSize, int maxHeaderListSize) { - this.dynamicTable = new DynamicTable(maxTableSize); - this.maxHeaderListSize = maxHeaderListSize; - } - - /** - * Set the maximum dynamic table size. - * - * @param maxSize new maximum size in bytes - */ - public void setMaxTableSize(int maxSize) { - dynamicTable.setMaxSize(maxSize); - } - - /** - * Decode a header block. - * - * @param data the HPACK-encoded header block - * @return list of decoded header fields - * @throws IOException if decoding fails - */ - public List decode(byte[] data) throws IOException { - return decode(data, 0, data.length); - } - - /** - * Decode a header block. - * - * @param data buffer containing HPACK-encoded header block - * @param offset start offset in buffer - * @param length number of bytes to decode - * @return list of decoded header fields - * @throws IOException if decoding fails - */ - public List decode(byte[] data, int offset, int length) throws IOException { - return decodeBlock(data, offset, length); - } - - /** - * Get a header field from the indexed tables. - */ - private HeaderField getIndexedField(int index) throws IOException { - if (index <= 0) { - throw new IOException("Invalid HPACK index: " + index); - } - - if (index <= StaticTable.SIZE) { - return StaticTable.get(index); - } else { - DynamicTable.HeaderField field = dynamicTable.get(index); - return new HeaderField(field.name(), field.value()); - } - } - - /** - * Get a header name from the indexed tables. - */ - private String getIndexedName(int index) throws IOException { - if (index <= 0) { - throw new IOException("Invalid HPACK name index: " + index); - } - - if (index <= StaticTable.SIZE) { - return StaticTable.get(index).name(); - } else { - return dynamicTable.get(index).name(); - } - } - - /** - * Decode an integer with the given prefix size. - * Updates decodePos and returns the decoded value. - */ - private int decodeInteger(byte[] data, int prefixBits) throws IOException { - if (decodePos >= data.length) { - throw new IOException("Incomplete HPACK integer: no data at position " + decodePos); - } - int maxPrefix = (1 << prefixBits) - 1; - int value = data[decodePos] & maxPrefix; - decodePos++; - - if (value < maxPrefix) { - return value; - } - - int shift = 0; - int b; - do { - if (decodePos >= data.length) { - throw new IOException("Incomplete HPACK integer"); - } - b = data[decodePos] & 0xFF; - decodePos++; - value += (b & 0x7F) << shift; - shift += 7; - - if (shift > 28) { - throw new IOException("HPACK integer overflow"); - } - } while ((b & 0x80) != 0); - - return value; - } - - /** - * Decode a string. - * Updates decodePos and returns the decoded string. - */ - private String decodeString(byte[] data) throws IOException { - if (decodePos >= data.length) { - throw new IOException("Incomplete HPACK string: no data at position " + decodePos); - } - - boolean huffman = (data[decodePos] & 0x80) != 0; - int length = decodeInteger(data, 7); - if (decodePos + length > data.length) { - throw new IOException("HPACK string length exceeds buffer"); - } - - String str; - if (huffman) { - str = Huffman.decode(data, decodePos, length); - } else { - str = new String(data, decodePos, length, StandardCharsets.ISO_8859_1); - } - decodePos += length; - - return str; - } - - /** - * Decode a header name string and intern strings. Updates decodePos and returns the interned name. - * - *

        NOTE: This method does NOT validate for uppercase. The caller must validate literal names using - * {@link #validateHeaderName(String)} on the raw decoded string BEFORE this method is called, - * to comply with RFC 9113 Section 8.2. - * - * @param data HPACK data buffer - * @param validate if true, validate for uppercase before interning - * @return interned header name - * @throws IOException if validation fails or decoding fails - */ - private String decodeHeaderName(byte[] data, boolean validate) throws IOException { - if (decodePos >= data.length) { - throw new IOException("Incomplete HPACK string: no data at position " + decodePos); - } - - boolean huffman = (data[decodePos] & 0x80) != 0; - int length = decodeInteger(data, 7); - - if (decodePos + length > data.length) { - throw new IOException("HPACK string length exceeds buffer"); - } - - String name; - if (huffman) { - // Huffman-encoded: decode then validate and normalize - name = Huffman.decode(data, decodePos, length); - if (validate) { - validateHeaderName(name); - } - name = HeaderNames.canonicalize(name); - } else { - // Raw bytes: validate before interning if needed - if (validate) { - for (int i = 0; i < length; i++) { - byte b = data[decodePos + i]; - if (b >= 'A' && b <= 'Z') { - throw new IOException("Header field name contains uppercase character"); - } - } - } - // Normalize directly (zero-copy for known headers) - name = HeaderNames.canonicalize(data, decodePos, length); - } - decodePos += length; - - return name; - } - - /** - * Decode a literal header field. Updates decodePos and returns the decoded header field. - * - *

        Validates literal header names per RFC 9113 Section 8.2. Indexed names are already validated - * (static table is RFC-defined, dynamic table entries were validated when added). - * - *

        Literal names are interned via {@link HeaderNames} for efficient pointer comparisons. - */ - private HeaderField decodeLiteralField(byte[] data, int prefixBits) throws IOException { - int nameIndex = decodeInteger(data, prefixBits); - - String name; - if (nameIndex > 0) { - // Indexed name, so already validated (static or dynamic table) - name = getIndexedName(nameIndex); - } else { - // Literal name: decode with validation (must not contain uppercase per RFC 9113) - name = decodeHeaderName(data, true); - } - - return new HeaderField(name, decodeString(data)); - } - - /** - * Decode a header block. - * - * @param data buffer containing HPACK-encoded header block - * @param offset start offset in buffer - * @param length number of bytes to decode - * @return list of decoded header fields - * @throws IOException if decoding fails - */ - public List decodeBlock(byte[] data, int offset, int length) throws IOException { - // Initial capacity of 12 avoids resizing for typical HTTP responses (5-15 headers) - List headers = new ArrayList<>(12); - decodePos = offset; - int end = offset + length; - int totalSize = 0; - boolean headerFieldSeen = false; - - while (decodePos < end) { - int b = data[decodePos] & 0xFF; - - HeaderField field; - if ((b & 0x80) != 0) { - // Indexed representation: 1xxxxxxx - // No validation needed - static table is RFC-defined lowercase, - // dynamic table entries were validated when added - int index = decodeInteger(data, 7); - field = getIndexedField(index); - headerFieldSeen = true; - } else if ((b & 0x40) != 0) { - // Literal with indexing: 01xxxxxx - // decodeLiteralField validates literal names - field = decodeLiteralField(data, 6); - dynamicTable.add(field.name(), field.value()); - headerFieldSeen = true; - } else if ((b & 0x20) != 0) { - // Dynamic table size update: 001xxxxx - // RFC 7541 Section 4.2: MUST occur at the beginning of header block - if (headerFieldSeen) { - throw new IOException("Dynamic table size update MUST occur at beginning of header block"); - } - int newSize = decodeInteger(data, 5); - dynamicTable.setMaxSize(newSize); - continue; - } else { - // Literal never indexed (0001xxxx) or without indexing (0000xxxx) - // decodeLiteralField validates literal names - field = decodeLiteralField(data, 4); - headerFieldSeen = true; - } - - // Check header list size per RFC 7541 Section 4.1. - // Since HPACK-decoded strings are ISO-8859-1, each char is one byte, - // so we can use length() directly instead of allocating byte arrays. - totalSize += field.name().length() + field.value().length() + 32; - if (totalSize > maxHeaderListSize) { - throw new IOException("Header list exceeds maximum size: " + totalSize + " > " + maxHeaderListSize); - } - - headers.add(field); - } - - return headers; - } - - /** - * Validate header field name per RFC 9113 Section 8.2. - * - * @param name header field name - * @throws IOException if name contains invalid characters - */ - private void validateHeaderName(String name) throws IOException { - for (int i = 0; i < name.length(); i++) { - char c = name.charAt(i); - // RFC 9113 Section 8.2: Field names MUST NOT contain uppercase characters - if (c >= 'A' && c <= 'Z') { - throw new IOException("Header field name contains uppercase character: '" + name); - } - } - } -} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTable.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTable.java deleted file mode 100644 index 8087c299c..000000000 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTable.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.http.client.h2.hpack; - -import software.amazon.smithy.java.http.api.HeaderNames; - -/** - * HPACK static table from RFC 7541 Appendix A. - * - *

        The static table consists of 61 predefined header field entries, where index 0 is unused. - * - *

        This implementation uses length-based bucketing for fast lookups with zero per-lookup - * allocations. Entries are grouped by header name length, so lookups only scan candidates - * with matching name length (typically 1-3 entries per bucket). - * - *

        Header names use constants from {@link HeaderNames} to enable pointer comparisons. - */ -final class StaticTable { - - private StaticTable() {} - - /** - * Number of entries in the static table. - */ - static final int SIZE = 61; - - /** - * Static table entries as pre-allocated HeaderField instances. - * Index 0 is unused (indices are 1-based per RFC 7541). - * - *

        Names use HeaderNameRegistry constants for pointer equality. - */ - private static final HeaderField[] ENTRIES = { - null, // Index 0 unused - new HeaderField(HeaderNames.PSEUDO_AUTHORITY, ""), // 1 - new HeaderField(HeaderNames.PSEUDO_METHOD, "GET"), // 2 - new HeaderField(HeaderNames.PSEUDO_METHOD, "POST"), // 3 - new HeaderField(HeaderNames.PSEUDO_PATH, "/"), // 4 - new HeaderField(HeaderNames.PSEUDO_PATH, "/index.html"), // 5 - new HeaderField(HeaderNames.PSEUDO_SCHEME, "http"), // 6 - new HeaderField(HeaderNames.PSEUDO_SCHEME, "https"), // 7 - new HeaderField(HeaderNames.PSEUDO_STATUS, "200"), // 8 - new HeaderField(HeaderNames.PSEUDO_STATUS, "204"), // 9 - new HeaderField(HeaderNames.PSEUDO_STATUS, "206"), // 10 - new HeaderField(HeaderNames.PSEUDO_STATUS, "304"), // 11 - new HeaderField(HeaderNames.PSEUDO_STATUS, "400"), // 12 - new HeaderField(HeaderNames.PSEUDO_STATUS, "404"), // 13 - new HeaderField(HeaderNames.PSEUDO_STATUS, "500"), // 14 - new HeaderField(HeaderNames.ACCEPT_CHARSET, ""), // 15 - new HeaderField(HeaderNames.ACCEPT_ENCODING, "gzip, deflate"), // 16 - new HeaderField(HeaderNames.ACCEPT_LANGUAGE, ""), // 17 - new HeaderField(HeaderNames.ACCEPT_RANGES, ""), // 18 - new HeaderField(HeaderNames.ACCEPT, ""), // 19 - new HeaderField(HeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, ""), // 20 - new HeaderField(HeaderNames.AGE, ""), // 21 - new HeaderField(HeaderNames.ALLOW, ""), // 22 - new HeaderField(HeaderNames.AUTHORIZATION, ""), // 23 - new HeaderField(HeaderNames.CACHE_CONTROL, ""), // 24 - new HeaderField(HeaderNames.CONTENT_DISPOSITION, ""), // 25 - new HeaderField(HeaderNames.CONTENT_ENCODING, ""), // 26 - new HeaderField(HeaderNames.CONTENT_LANGUAGE, ""), // 27 - new HeaderField(HeaderNames.CONTENT_LENGTH, ""), // 28 - new HeaderField(HeaderNames.CONTENT_LOCATION, ""), // 29 - new HeaderField(HeaderNames.CONTENT_RANGE, ""), // 30 - new HeaderField(HeaderNames.CONTENT_TYPE, ""), // 31 - new HeaderField(HeaderNames.COOKIE, ""), // 32 - new HeaderField(HeaderNames.DATE, ""), // 33 - new HeaderField(HeaderNames.ETAG, ""), // 34 - new HeaderField(HeaderNames.EXPECT, ""), // 35 - new HeaderField(HeaderNames.EXPIRES, ""), // 36 - new HeaderField(HeaderNames.FROM, ""), // 37 - new HeaderField(HeaderNames.HOST, ""), // 38 - new HeaderField(HeaderNames.IF_MATCH, ""), // 39 - new HeaderField(HeaderNames.IF_MODIFIED_SINCE, ""), // 40 - new HeaderField(HeaderNames.IF_NONE_MATCH, ""), // 41 - new HeaderField(HeaderNames.IF_RANGE, ""), // 42 - new HeaderField(HeaderNames.IF_UNMODIFIED_SINCE, ""), // 43 - new HeaderField(HeaderNames.LAST_MODIFIED, ""), // 44 - new HeaderField(HeaderNames.LINK, ""), // 45 - new HeaderField(HeaderNames.LOCATION, ""), // 46 - new HeaderField(HeaderNames.MAX_FORWARDS, ""), // 47 - new HeaderField(HeaderNames.PROXY_AUTHENTICATE, ""), // 48 - new HeaderField(HeaderNames.PROXY_AUTHORIZATION, ""), // 49 - new HeaderField(HeaderNames.RANGE, ""), // 50 - new HeaderField(HeaderNames.REFERER, ""), // 51 - new HeaderField(HeaderNames.REFRESH, ""), // 52 - new HeaderField(HeaderNames.RETRY_AFTER, ""), // 53 - new HeaderField(HeaderNames.SERVER, ""), // 54 - new HeaderField(HeaderNames.SET_COOKIE, ""), // 55 - new HeaderField(HeaderNames.STRICT_TRANSPORT_SECURITY, ""), // 56 - new HeaderField(HeaderNames.TRANSFER_ENCODING, ""), // 57 - new HeaderField(HeaderNames.USER_AGENT, ""), // 58 - new HeaderField(HeaderNames.VARY, ""), // 59 - new HeaderField(HeaderNames.VIA, ""), // 60 - new HeaderField(HeaderNames.WWW_AUTHENTICATE, "") // 61 - }; - - /** - * Maximum header name length in the static table. - */ - private static final int MAX_NAME_LEN; - - /** - * Empty bucket for lengths with no entries (avoids null checks in lookups). - */ - private static final int[] EMPTY_BUCKET = new int[0]; - - /** - * Buckets of static table indices grouped by header name length. - * NAME_BUCKETS_BY_LEN[len] contains indices of entries whose name has that length. - * Empty buckets use EMPTY_BUCKET to avoid null checks. - */ - private static final int[][] NAME_BUCKETS_BY_LEN; - - static { - // Find max name length - int maxLen = 0; - for (int i = 1; i <= SIZE; i++) { - int len = ENTRIES[i].name().length(); - if (len > maxLen) { - maxLen = len; - } - } - MAX_NAME_LEN = maxLen; - - // First pass: count entries per length - int[] counts = new int[MAX_NAME_LEN + 1]; - for (int i = 1; i <= SIZE; i++) { - counts[ENTRIES[i].name().length()]++; - } - - // Allocate buckets (empty bucket for lengths with no entries) - int[][] buckets = new int[MAX_NAME_LEN + 1][]; - for (int len = 0; len <= MAX_NAME_LEN; len++) { - buckets[len] = counts[len] > 0 ? new int[counts[len]] : EMPTY_BUCKET; - } - - // Second pass: fill buckets - int[] pos = new int[MAX_NAME_LEN + 1]; - for (int i = 1; i <= SIZE; i++) { - int len = ENTRIES[i].name().length(); - buckets[len][pos[len]++] = i; - } - - NAME_BUCKETS_BY_LEN = buckets; - } - - /** - * Get the header field at the given index. - * - * @param index 1-based index into static table - * @return header field - */ - static HeaderField get(int index) { - return ENTRIES[index]; - } - - /** - * Find index for a full match (name + value). - * - * @param name header name - * @param value header value - * @return index if found, -1 otherwise - */ - static int findFullMatch(String name, String value) { - int len = name.length(); - if (len > MAX_NAME_LEN) { - return -1; - } - for (int idx : NAME_BUCKETS_BY_LEN[len]) { - HeaderField e = ENTRIES[idx]; - var entryName = e.name(); - if ((entryName == name || entryName.equals(name)) && e.value().equals(value)) { - return idx; - } - } - return -1; - } - - /** - * Find index for a name-only match. - * - * @param name header name - * @return index of first entry with this name, -1 if not found - */ - static int findNameMatch(String name) { - int len = name.length(); - if (len <= MAX_NAME_LEN) { - for (int idx : NAME_BUCKETS_BY_LEN[len]) { - var entry = ENTRIES[idx]; - var entryName = entry.name(); - if (entryName == name || entryName.equals(name)) { - return idx; - } - } - } - return -1; - } -} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessorTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessorTest.java index ede5e48df..1b09797e9 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessorTest.java +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2ResponseHeaderProcessorTest.java @@ -9,22 +9,24 @@ import java.io.IOException; import java.util.List; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import software.amazon.smithy.java.http.client.h2.hpack.HeaderField; class H2ResponseHeaderProcessorTest { - private static HeaderField hf(String name, String value) { - return new HeaderField(name, value); + // Helper to create flat header list + private static List headers(String... pairs) { + return List.of(pairs); } @Test void validResponseHeaders() throws IOException { - var fields = List.of( - hf(":status", "200"), - hf("content-type", "application/json"), - hf("content-length", "42")); + var fields = headers( + ":status", + "200", + "content-type", + "application/json", + "content-length", + "42"); var result = H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false); @@ -35,7 +37,8 @@ void validResponseHeaders() throws IOException { @Test void informationalResponse() throws IOException { - var fields = List.of(hf(":status", "100")); + var fields = headers(":status", "100"); + var result = H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false); assertTrue(result.isInformational()); @@ -43,16 +46,16 @@ void informationalResponse() throws IOException { @Test void informationalResponseWithEndStreamThrows() { - var fields = List.of(hf(":status", "100")); + var fields = headers(":status", "100"); + var ex = assertThrows(H2Exception.class, () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, true)); - assertTrue(ex.getMessage().contains("1xx response must not have END_STREAM")); } @Test void missingStatusThrows() { - var fields = List.of(hf("content-type", "text/plain")); + var fields = headers("content-type", "text/plain"); assertThrows(IOException.class, () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); @@ -60,16 +63,16 @@ void missingStatusThrows() { @Test void duplicateStatusThrows() { - var fields = List.of(hf(":status", "200"), hf(":status", "201")); + var fields = headers(":status", "200", ":status", "201"); + var ex = assertThrows(H2Exception.class, () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); - assertTrue(ex.getMessage().contains("single :status")); } @Test void invalidStatusValueThrows() { - var fields = List.of(hf(":status", "abc")); + var fields = headers(":status", "abc"); assertThrows(IOException.class, () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); @@ -77,79 +80,92 @@ void invalidStatusValueThrows() { @Test void pseudoHeaderAfterRegularHeaderThrows() { - var fields = List.of( - hf(":status", "200"), - hf("content-type", "text/plain"), - hf(":unknown", "value")); + var fields = headers( + ":status", + "200", + "content-type", + "text/plain", + ":unknown", + "value"); + var ex = assertThrows(H2Exception.class, () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); - assertTrue(ex.getMessage().contains("appears after regular header")); } @Test void requestPseudoHeaderInResponseThrows() { - var fields = List.of(hf(":status", "200"), hf(":method", "GET")); + var fields = headers(":status", "200", ":method", "GET"); + var ex = assertThrows(H2Exception.class, () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); - assertTrue(ex.getMessage().contains("Request pseudo-header")); } @Test void unknownPseudoHeaderThrows() { - var fields = List.of(hf(":status", "200"), hf(":unknown", "value")); + var fields = headers(":status", "200", ":unknown", "value"); + var ex = assertThrows(H2Exception.class, () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); - assertTrue(ex.getMessage().contains("Unknown pseudo-header")); } @Test void invalidContentLengthThrows() { - var fields = List.of(hf(":status", "200"), hf("content-length", "not-a-number")); + var fields = headers(":status", "200", "content-length", "not-a-number"); + var ex = assertThrows(H2Exception.class, () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); - assertTrue(ex.getMessage().contains("Invalid Content-Length")); } @Test void multipleConflictingContentLengthThrows() { - var fields = List.of( - hf(":status", "200"), - hf("content-length", "100"), - hf("content-length", "200")); + var fields = headers( + ":status", + "200", + "content-length", + "100", + "content-length", + "200"); + var ex = assertThrows(H2Exception.class, () -> H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false)); - assertTrue(ex.getMessage().contains("Multiple Content-Length")); } @Test void duplicateIdenticalContentLengthAllowed() throws IOException { - var fields = List.of( - hf(":status", "200"), - hf("content-length", "100"), - hf("content-length", "100")); - var result = H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false); + var fields = headers( + ":status", + "200", + "content-length", + "100", + "content-length", + "100"); + var result = H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false); assertEquals(100, result.contentLength()); } @Test void noContentLengthReturnsMinusOne() throws IOException { - var fields = List.of(hf(":status", "200")); - var result = H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false); + var fields = headers(":status", "200"); + var result = H2ResponseHeaderProcessor.processResponseHeaders(fields, 1, false); assertEquals(-1, result.contentLength()); } + // === processTrailers === + @Test void validTrailers() throws IOException { - var fields = List.of( - hf("x-checksum", "abc123"), - hf("x-request-id", "req-456")); + var fields = headers( + "x-checksum", + "abc123", + "x-request-id", + "req-456"); var trailers = H2ResponseHeaderProcessor.processTrailers(fields, 1); @@ -159,41 +175,35 @@ void validTrailers() throws IOException { @Test void trailerWithPseudoHeaderThrows() { - var fields = List.of(hf(":status", "200")); + var fields = headers(":status", "200"); + var ex = assertThrows(H2Exception.class, () -> H2ResponseHeaderProcessor.processTrailers(fields, 1)); - assertTrue(ex.getMessage().contains("Trailer contains pseudo-header")); } @Test void emptyTrailersAllowed() throws IOException { var trailers = H2ResponseHeaderProcessor.processTrailers(List.of(), 1); - assertTrue(trailers.map().isEmpty()); } + // === validateContentLength === + @Test - void contentLengthMatchPasses() { - Assertions.assertDoesNotThrow(() -> { - H2ResponseHeaderProcessor.validateContentLength(100, 100, 1); - }); + void contentLengthMatchPasses() throws IOException { + H2ResponseHeaderProcessor.validateContentLength(100, 100, 1); } @Test void contentLengthMismatchThrows() { var ex = assertThrows(H2Exception.class, () -> H2ResponseHeaderProcessor.validateContentLength(100, 50, 1)); - assertTrue(ex.getMessage().contains("Content-Length mismatch")); - assertTrue(ex.getMessage().contains("expected 100")); - assertTrue(ex.getMessage().contains("received 50")); } @Test - void noContentLengthSkipsValidation() { - Assertions.assertDoesNotThrow(() -> { - H2ResponseHeaderProcessor.validateContentLength(-1, 999, 1); - }); + void noContentLengthSkipsValidation() throws IOException { + H2ResponseHeaderProcessor.validateContentLength(-1, 999, 1); } } diff --git a/http/http-hpack/build.gradle.kts b/http/http-hpack/build.gradle.kts new file mode 100644 index 000000000..6757e09f5 --- /dev/null +++ b/http/http-hpack/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("smithy-java.module-conventions") +} + +description = "HPACK codec for HTTP/2 header compression" + +extra["displayName"] = "Smithy :: Java :: HTTP :: HPACK" +extra["moduleName"] = "software.amazon.smithy.java.http.hpack" + +dependencies { + api(project(":http:http-api")) + + // Jackson for HPACK test suite JSON parsing + testImplementation("com.fasterxml.jackson.core:jackson-databind:2.18.2") +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTable.java b/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/DynamicTable.java similarity index 51% rename from http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTable.java rename to http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/DynamicTable.java index d8100b971..0bc10b450 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTable.java +++ b/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/DynamicTable.java @@ -3,45 +3,40 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.h2.hpack; +package software.amazon.smithy.java.http.hpack; -import java.util.ArrayDeque; -import java.util.Deque; +import java.util.ArrayList; /** * HPACK dynamic table implementation from RFC 7541 Section 2.3.2. * *

        The dynamic table is a FIFO queue of header field entries. New entries - * are added at the front (lowest index), and entries are evicted from the - * back (highest index) when the table size exceeds the maximum. + * are added at index 62 (lowest dynamic index), and older entries are evicted + * first when the table size exceeds the maximum. * *

        Dynamic table indices start at 62 (after the 61 static table entries). * Index 62 is the most recently added entry. * - *

        This implementation uses linear scans for lookups. The typical dynamic table - * size is small (< 128 entries with default 4KB limit) so linear scans have good - * cache locality and avoid the overhead of maintaining index maps that shift on - * every add operation. + *

        Entries are stored as interleaved name/value pairs. We use an ArrayList + * with reverse indexing: new entries append to the end (O(1)), and index 62 + * maps to the last pair. Eviction removes from the front. * *

        Header names must be lowercase as required by HTTP/2 (RFC 7540 Section 8.1.2). - * Name matching uses case-sensitive String.equals(). */ final class DynamicTable { /** - * Each entry has 32 bytes of overhead. + * Each entry has 32 bytes of overhead per RFC 7541 Section 4.1. */ private static final int ENTRY_OVERHEAD = 32; - private final Deque entries = new ArrayDeque<>(); + // Interleaved storage: [name0, value0, name1, value1, ...] + // Newest entries at the END (reverse of logical HPACK order) + private final ArrayList entries = new ArrayList<>(); + private int numEntries = 0; private int currentSize = 0; private int maxSize; - /** - * Header field entry with cached size to avoid recomputation during eviction. - */ - record HeaderField(String name, String value, int size) {} - /** * Create a dynamic table with the given maximum size. * @@ -57,7 +52,7 @@ record HeaderField(String name, String value, int size) {} * @return entry count */ int length() { - return entries.size(); + return numEntries; } /** @@ -105,7 +100,8 @@ void setMaxSize(int newMaxSize) { void add(String name, String value) { int entrySize = entrySize(name, value); - // If entry does not fit even in empty table, don't add it, but still evict to make room as per spec + // RFC 7541 Section 4.4: "an attempt to add an entry larger than the maximum size + // causes the table to be emptied of all existing entries and results in an empty table" if (entrySize > maxSize) { clear(); return; @@ -114,35 +110,54 @@ void add(String name, String value) { // Evict entries until there's room evictToSize(maxSize - entrySize); - // Add new entry at front with cached size - entries.addFirst(new HeaderField(name, value, entrySize)); + // Append to end (newest = highest array index, but lowest HPACK index) + entries.add(name); + entries.add(value); currentSize += entrySize; + numEntries++; } /** - * Get header field at the given index. + * Get header name at the given index. * * @param index dynamic table index (62 + offset) - * @return header field + * @return header name * @throws IndexOutOfBoundsException if index is out of range */ - HeaderField get(int index) { - int offset = index - StaticTable.SIZE - 1; // Convert to 0-based offset - if (offset < 0 || offset >= entries.size()) { - throw new IndexOutOfBoundsException("Dynamic table index out of range: " - + index + " (table has " + entries.size() + " entries)"); - } + String getName(int index) { + return entries.get(toArrayIndex(index)); + } - // Linear scan to find entry at offset - int i = 0; - for (HeaderField field : entries) { - if (i++ == offset) { - return field; - } + /** + * Get header value at the given index. + * + * @param index dynamic table index (62 + offset) + * @return header value + * @throws IndexOutOfBoundsException if index is out of range + */ + String getValue(int index) { + return entries.get(toArrayIndex(index) + 1); + } + + /** + * Convert HPACK index to array index. + * HPACK index 62 = newest entry = last pair in array. + */ + private int toArrayIndex(int hpackIndex) { + int offset = hpackIndex - StaticTable.SIZE - 1; + if (offset < 0 || offset >= numEntries) { + throw new IndexOutOfBoundsException("Dynamic table index out of range: " + + hpackIndex + " (table has " + numEntries + " entries)"); } + // Reverse: offset 0 -> last pair, offset 1 -> second-to-last, etc. + return entries.size() - 2 - (offset * 2); + } - // Should never reach here given bounds check above - throw new AssertionError("Unreachable: offset in range but entry not found"); + /** + * Convert an array index to an HPACK dynamic table index. + */ + private int toHpackIndex(int arrayIndex) { + return StaticTable.SIZE + 1 + (entries.size() - 2 - arrayIndex) / 2; } /** @@ -153,12 +168,11 @@ HeaderField get(int index) { * @return dynamic table index (62+) if found, -1 otherwise */ int findFullMatch(String name, String value) { - int index = StaticTable.SIZE + 1; - for (HeaderField field : entries) { - if (field.name().equals(name) && field.value().equals(value)) { - return index; + // Search from newest (end) to oldest (start) + for (int i = entries.size() - 2; i >= 0; i -= 2) { + if (entries.get(i).equals(name) && entries.get(i + 1).equals(value)) { + return toHpackIndex(i); } - index++; } return -1; } @@ -170,12 +184,10 @@ int findFullMatch(String name, String value) { * @return dynamic table index (62+) if found, -1 otherwise */ int findNameMatch(String name) { - int index = StaticTable.SIZE + 1; - for (HeaderField field : entries) { - if (field.name().equals(name)) { - return index; + for (int i = entries.size() - 2; i >= 0; i -= 2) { + if (entries.get(i).equals(name)) { + return toHpackIndex(i); } - index++; } return -1; } @@ -186,23 +198,35 @@ int findNameMatch(String name) { void clear() { entries.clear(); currentSize = 0; + numEntries = 0; } /** * Calculate the size of an entry per RFC 7541 Section 4.1. - * Size = length(name) in octets + length(value) in octets + 32 + * Size = length(name) + length(value) + 32 * - *

        HTTP/2 header names are lowercase ASCII, and values are effectively ASCII/Latin-1, - * so we count chars as bytes directly (avoids getBytes allocation per insertion). + * @param name header name + * @param value header value + * @return entry size in bytes */ static int entrySize(String name, String value) { return name.length() + value.length() + ENTRY_OVERHEAD; } private void evictToSize(int targetSize) { - while (currentSize > targetSize && !entries.isEmpty()) { - HeaderField evicted = entries.removeLast(); - currentSize -= evicted.size(); + int removeCount = 0; + int removedSize = 0; + + // Efficiently resize in one-shot. + while (currentSize - removedSize > targetSize && removeCount < entries.size()) { + removedSize += entrySize(entries.get(removeCount), entries.get(removeCount + 1)); + removeCount += 2; + } + + if (removeCount > 0) { + entries.subList(0, removeCount).clear(); + currentSize -= removedSize; + numEntries -= removeCount / 2; } } } diff --git a/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/HpackDecoder.java b/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/HpackDecoder.java new file mode 100644 index 000000000..4ff5fad39 --- /dev/null +++ b/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/HpackDecoder.java @@ -0,0 +1,277 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.hpack; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import software.amazon.smithy.java.http.api.HeaderNames; + +/** + * HPACK decoder for HTTP/2 header decompression (RFC 7541). + * + *

        Thread safety: This class is not thread-safe. Each HTTP/2 connection should have + * its own decoder instance to maintain dynamic table state. + */ +public final class HpackDecoder { + + private static final int DEFAULT_MAX_TABLE_SIZE = 4096; + private static final int DEFAULT_MAX_HEADER_LIST_SIZE = 8192; + + private final DynamicTable dynamicTable; + private final int maxHeaderListSize; + private int maxTableSize; + + /** Current position during decoding, reset at start of each decode call. */ + private int decodePos; + /** End of current decode region. */ + private int limit; + + /** + * Create a decoder with default limits (4096 byte table, 8192 byte header list). + */ + public HpackDecoder() { + this(DEFAULT_MAX_TABLE_SIZE, DEFAULT_MAX_HEADER_LIST_SIZE); + } + + /** + * Create a decoder with the given maximum dynamic table size. + * + * @param maxTableSize maximum dynamic table size in bytes + */ + public HpackDecoder(int maxTableSize) { + this(maxTableSize, DEFAULT_MAX_HEADER_LIST_SIZE); + } + + /** + * Create a decoder with the given limits. + * + * @param maxTableSize maximum dynamic table size in bytes + * @param maxHeaderListSize maximum size of decoded header list + */ + public HpackDecoder(int maxTableSize, int maxHeaderListSize) { + this.dynamicTable = new DynamicTable(maxTableSize); + this.maxTableSize = maxTableSize; + this.maxHeaderListSize = maxHeaderListSize; + } + + /** + * Set the maximum dynamic table size. + * + * @param maxSize new maximum size in bytes + */ + public void setMaxTableSize(int maxSize) { + this.maxTableSize = maxSize; + dynamicTable.setMaxSize(maxSize); + } + + /** + * Decode a header block. + * + * @param data the HPACK-encoded header block + * @return flat list of headers: [name0, value0, name1, value1, ...] + * @throws IOException if decoding fails + */ + public List decode(byte[] data) throws IOException { + return decode(data, 0, data.length); + } + + /** + * Decode a header block. + * + * @param data buffer containing HPACK-encoded header block + * @param offset start offset in buffer + * @param length number of bytes to decode + * @return flat list of headers: [name0, value0, name1, value1, ...] + * @throws IOException if decoding fails + */ + public List decode(byte[] data, int offset, int length) throws IOException { + if (length == 0) { + return List.of(); + } + + // ~12 headers * 2 = 24 + List headers = new ArrayList<>(24); + decodePos = offset; + limit = offset + length; + int totalSize = 0; + boolean headerFieldSeen = false; + + while (decodePos < limit) { + int b = data[decodePos] & 0xFF; + + String name, value; + if ((b & 0x80) != 0) { + // Indexed representation: 1xxxxxxx + int index = decodeInteger(data, 7); + if (index <= 0) { + throw new IOException("Invalid HPACK index: " + index); + } else if (index <= StaticTable.SIZE) { + name = StaticTable.getName(index); + value = StaticTable.getValue(index); + } else { + name = dynamicTable.getName(index); + value = dynamicTable.getValue(index); + } + headerFieldSeen = true; + } else if ((b & 0x40) != 0) { + // Literal with indexing: 01xxxxxx + int nameIndex = decodeInteger(data, 6); + name = nameIndex > 0 ? getIndexedName(nameIndex) : decodeHeaderName(data); + value = decodeString(data); + dynamicTable.add(name, value); + headerFieldSeen = true; + } else if ((b & 0x20) != 0) { + // Dynamic table size update: 001xxxxx + // RFC 7541 Section 4.2: "This dynamic table size update MUST occur at the beginning of the first + // header block following the change to the dynamic table size" + if (headerFieldSeen) { + throw new IOException("Dynamic table size update MUST occur at beginning of header block"); + } + int newSize = decodeInteger(data, 5); + if (newSize > maxTableSize) { + throw new IOException( + "Dynamic table size update " + newSize + " exceeds configured maximum " + maxTableSize); + } + dynamicTable.setMaxSize(newSize); + continue; + } else { + // Literal never indexed (0001xxxx) or without indexing (0000xxxx) + int nameIndex = decodeInteger(data, 4); + name = nameIndex > 0 ? getIndexedName(nameIndex) : decodeHeaderName(data); + value = decodeString(data); + headerFieldSeen = true; + } + + // Check header list size + totalSize += name.length() + value.length() + 32; + if (totalSize > maxHeaderListSize) { + throw new IOException("Header list exceeds maximum size: " + totalSize + " > " + maxHeaderListSize); + } + + headers.add(name); + headers.add(value); + } + + return headers; + } + + /** + * Get a header name from the indexed tables. + * + * @param index table index (1-61 for static, 62+ for dynamic) + * @return header name + * @throws IOException if index is invalid + */ + private String getIndexedName(int index) throws IOException { + if (index <= 0) { + throw new IOException("Invalid HPACK name index: " + index); + } + return index <= StaticTable.SIZE ? StaticTable.getName(index) : dynamicTable.getName(index); + } + + /** + * Decode an integer with the given prefix size. Updates decodePos and returns the decoded value. + * + * @param data buffer containing encoded integer + * @param prefixBits number of prefix bits (1-8) + * @return decoded integer value + * @throws IOException if integer is incomplete or overflows + */ + private int decodeInteger(byte[] data, int prefixBits) throws IOException { + if (decodePos >= limit) { + throw new IOException("Incomplete HPACK integer"); + } + + int maxPrefix = (1 << prefixBits) - 1; + int value = data[decodePos] & maxPrefix; + decodePos++; + + if (value < maxPrefix) { + return value; + } + + int shift = 0; + int b; + do { + if (decodePos >= limit) { + throw new IOException("Incomplete HPACK integer"); + } + b = data[decodePos++] & 0xFF; + if (shift >= 28) { + throw new IOException("HPACK integer overflow"); + } + value += (b & 0x7F) << shift; + shift += 7; + } while ((b & 0x80) != 0); + + return value; + } + + /** + * Decode a string literal. + * Updates decodePos and returns the decoded string. + * + * @param data buffer containing encoded string + * @return decoded string + * @throws IOException if string is incomplete or invalid + */ + private String decodeString(byte[] data) throws IOException { + if (decodePos >= limit) { + throw new IOException("Incomplete HPACK string"); + } + + boolean isHuffmanEncoded = (data[decodePos] & 0x80) != 0; + int length = decodeStringLength(data); + int start = decodePos; + decodePos += length; + return isHuffmanEncoded + ? Huffman.decode(data, start, length) + : new String(data, start, length, StandardCharsets.ISO_8859_1); + } + + private int decodeStringLength(byte[] data) throws IOException { + int length = decodeInteger(data, 7); + if (decodePos + length > limit) { + throw new IOException("HPACK string length exceeds buffer"); + } + return length; + } + + /** + * Decode a header name string with validation and interning. + * + *

        Validates that literal names do not contain uppercase characters, then interns via {@link HeaderNames}. + * + * @param data buffer containing encoded name + * @return interned header name + * @throws IOException if validation fails or decoding fails + */ + private String decodeHeaderName(byte[] data) throws IOException { + if (decodePos >= limit) { + throw new IOException("Incomplete HPACK string"); + } + + boolean isHuffmanEncoded = (data[decodePos] & 0x80) != 0; + int length = decodeStringLength(data); + int start = decodePos; + decodePos += length; + + if (isHuffmanEncoded) { + return Huffman.decodeHeaderName(data, start, length); + } + + for (int i = 0; i < length; i++) { + byte b = data[start + i]; + if (b >= 'A' && b <= 'Z') { + throw new IOException("Header name contains uppercase"); + } + } + + return HeaderNames.canonicalize(data, start, length); + } +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoder.java b/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/HpackEncoder.java similarity index 75% rename from http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoder.java rename to http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/HpackEncoder.java index e474a2370..286437cbd 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoder.java +++ b/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/HpackEncoder.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.h2.hpack; +package software.amazon.smithy.java.http.hpack; import java.io.IOException; import java.io.OutputStream; @@ -24,19 +24,36 @@ public final class HpackEncoder { "proxy-authorization", "set-cookie"); + // HPACK representation type prefixes (RFC 7541 Section 6) + private static final int PREFIX_INDEXED = 0x80; // 1xxxxxxx + private static final int PREFIX_LITERAL_INDEXED = 0x40; // 01xxxxxx + private static final int PREFIX_SIZE_UPDATE = 0x20; // 001xxxxx + private static final int PREFIX_LITERAL_NEVER = 0x10; // 0001xxxx + + private static final int DEFAULT_MAX_TABLE_SIZE = 4096; + private final DynamicTable dynamicTable; private final boolean useHuffman; - // Track pending table size update to emit at start of next header block (RFC 7541 Section 4.2) - // -1 means no update pending + // Track pending table size updates to emit at start of next header block (RFC 7541 Section 4.2). + // If multiple size changes occur before a header block, we must emit the minimum reached + // and then the final size, to ensure the decoder evicts the same entries we did. private int pendingTableSizeUpdate = -1; + private int minPendingTableSize = -1; // Reusable scratch buffer for string encoding to avoid per-string allocation. // Typical header values are < 256 bytes; buffer grows if needed. private byte[] stringBuf = new byte[256]; /** - * Create an encoder with the given maximum dynamic table size. + * Create an encoder with default limits (4096 byte table) and Huffman encoding enabled. + */ + public HpackEncoder() { + this(DEFAULT_MAX_TABLE_SIZE, true); + } + + /** + * Create an encoder with the given maximum dynamic table size and Huffman encoding enabled. * * @param maxTableSize maximum dynamic table size in bytes */ @@ -58,20 +75,23 @@ public HpackEncoder(int maxTableSize, boolean useHuffman) { /** * Set the maximum dynamic table size. * - *

        This should be called when receiving a SETTINGS frame with - * SETTINGS_HEADER_TABLE_SIZE. Per RFC 7541 Section 4.2, the encoder - * MUST signal the change to the decoder at the start of the next header block - * (only if the size actually changed). + *

        This should be called when receiving a SETTINGS frame with SETTINGS_HEADER_TABLE_SIZE. + * Per RFC 7541 Section 4.2, the encoder MUST signal the change to the decoder at the start of the next + * header block (only if the size actually changed). * * @param maxSize new maximum size in bytes */ public void setMaxTableSize(int maxSize) { int currentMaxSize = dynamicTable.maxSize(); - // Only emit table size update if the size actually changed + if (maxSize != currentMaxSize) { - // Apply immediately to dynamic table (evicts entries if needed) dynamicTable.setMaxSize(maxSize); - // Mark that we need to emit a table size update in the next header block + // Track the minimum size reached since last header block + if (pendingTableSizeUpdate != -1) { + minPendingTableSize = Math.min(minPendingTableSize, maxSize); + } else { + minPendingTableSize = maxSize; + } pendingTableSizeUpdate = maxSize; } } @@ -79,22 +99,24 @@ public void setMaxTableSize(int maxSize) { /** * Emit any pending dynamic table size update. * - *

        Per RFC 7541 Section 4.2, when SETTINGS_HEADER_TABLE_SIZE is received, - * the encoder MUST signal the change at the start of the next header block - * by emitting a dynamic table size update instruction. + *

        Per RFC 7541 Section 4.2, when SETTINGS_HEADER_TABLE_SIZE is received, the encoder MUST signal the change + * at the start of the next header block by emitting a dynamic table size update instruction. * - *

        This method MUST be called once at the start of each header block - * (before encoding any headers). + *

        This method MUST be called once at the start of each header block (before encoding any headers). * * @param out output stream to write the update to * @throws IOException if writing fails */ public void beginHeaderBlock(OutputStream out) throws IOException { if (pendingTableSizeUpdate >= 0) { - // Emit dynamic table size update (RFC 7541 Section 6.3) - // Format: 001xxxxx (5-bit prefix for max size) - encodeInteger(out, pendingTableSizeUpdate, 5, 0x20); + // RFC 7541 Section 4.2: If size was reduced then raised, emit the minimum first + // to ensure the decoder evicts the same entries we did. + if (minPendingTableSize < pendingTableSizeUpdate) { + encodeInteger(out, minPendingTableSize, 5, PREFIX_SIZE_UPDATE); + } + encodeInteger(out, pendingTableSizeUpdate, 5, PREFIX_SIZE_UPDATE); pendingTableSizeUpdate = -1; + minPendingTableSize = -1; } } @@ -146,7 +168,7 @@ public void encodeHeader(OutputStream out, String name, String value, boolean se * Format: 1xxxxxxx (7-bit prefix) */ private void encodeIndexed(OutputStream out, int index) throws IOException { - encodeInteger(out, index, 7, 0x80); + encodeInteger(out, index, 7, PREFIX_INDEXED); } /** @@ -156,10 +178,10 @@ private void encodeLiteralWithIndexing(OutputStream out, int nameIndex, String n throws IOException { if (nameIndex > 0) { // Indexed name - encodeInteger(out, nameIndex, 6, 0x40); + encodeInteger(out, nameIndex, 6, PREFIX_LITERAL_INDEXED); } else { // New name - out.write(0x40); + out.write(PREFIX_LITERAL_INDEXED); encodeString(out, name); } encodeString(out, value); @@ -171,9 +193,9 @@ private void encodeLiteralWithIndexing(OutputStream out, int nameIndex, String n private void encodeLiteralNeverIndexed(OutputStream out, int nameIndex, String name, String value) throws IOException { if (nameIndex > 0) { - encodeInteger(out, nameIndex, 4, 0x10); + encodeInteger(out, nameIndex, 4, PREFIX_LITERAL_NEVER); } else { - out.write(0x10); + out.write(PREFIX_LITERAL_NEVER); encodeString(out, name); } encodeString(out, value); @@ -208,7 +230,6 @@ private void encodeInteger(OutputStream out, int value, int prefixBits, int pref /** * Encode a string, using Huffman encoding if it saves space. - * RFC 7541 Section 5.2 */ @SuppressWarnings("deprecation") private void encodeString(OutputStream out, String str) throws IOException { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/Huffman.java b/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/Huffman.java similarity index 89% rename from http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/Huffman.java rename to http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/Huffman.java index f63ee2d48..a2424956f 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h2/hpack/Huffman.java +++ b/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/Huffman.java @@ -3,12 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.h2.hpack; +package software.amazon.smithy.java.http.hpack; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import software.amazon.smithy.java.http.api.HeaderNames; /** * HPACK Huffman encoding/decoding from RFC 7541 Appendix B. @@ -544,10 +545,6 @@ private Huffman() {} 28 }; - // EOS (end-of-string) symbol code - private static final int EOS_CODE = 0x3fffffff; - private static final int EOS_LENGTH = 30; - // Decode flags private static final int FLAG_EMIT = 0x01; // Emit a decoded byte private static final int FLAG_ACCEPTED = 0x02; // Valid end state @@ -594,11 +591,9 @@ static void encode(byte[] data, int offset, int length, OutputStream out) throws } } - // Pad with EOS prefix to byte boundary + // Pad with EOS (all 1s) to byte boundary if (bits > 0) { - current <<= (8 - bits); - current |= (EOS_CODE >> (EOS_LENGTH - (8 - bits))); - out.write((int) current); + out.write((int) ((current << (8 - bits)) | (0xFF >> bits))); } } @@ -629,8 +624,31 @@ static int encodedLength(byte[] data, int offset, int length) { * @throws IOException if decoding fails (invalid Huffman sequence) */ static String decode(byte[] data, int offset, int length) throws IOException { - // HPACK Huffman expands by at most ~1.6x for typical headers + // Safe: shortest HPACK Huffman code is 5 bits, so max expansion is 8/5 = 1.6x < 2x + byte[] buf = new byte[length * 2]; + int pos = decodeBytes(data, offset, length, false, buf); + return new String(buf, 0, pos, StandardCharsets.ISO_8859_1); + } + + /** + * Decode a Huffman-encoded header name, validating no uppercase and canonicalizing. + */ + static String decodeHeaderName(byte[] data, int offset, int length) throws IOException { + // Safe: shortest HPACK Huffman code is 5 bits, so max expansion is 8/5 = 1.6x < 2x byte[] buf = new byte[length * 2]; + int pos = decodeBytes(data, offset, length, true, buf); + return HeaderNames.canonicalize(buf, 0, pos); + } + + /** + * Decode Huffman-encoded bytes into the provided buffer. + * + * @param buf output buffer, must be at least {@code length * 2} bytes + * @return number of decoded bytes written to buf + */ + private static int decodeBytes(byte[] data, int offset, int length, boolean validateName, byte[] buf) + throws IOException { + assert buf.length >= length * 2 : "buffer too small for Huffman decode"; int pos = 0; int state = 0; boolean accepted = true; @@ -643,31 +661,14 @@ static String decode(byte[] data, int offset, int length) throws IOException { state = DECODE_TABLE[index][0]; int flags = DECODE_TABLE[index][1]; - if ((flags & FLAG_FAIL) != 0) { - throw new IOException("Invalid Huffman encoding"); - } - if ((flags & FLAG_EMIT) != 0) { - if (pos >= buf.length) { - buf = Arrays.copyOf(buf, buf.length * 2); - } - buf[pos++] = (byte) DECODE_TABLE[index][2]; - } + pos = processNibble(validateName, buf, pos, index, flags); // Process low nibble index = (state << 4) | (b & 0x0F); state = DECODE_TABLE[index][0]; flags = DECODE_TABLE[index][1]; - if ((flags & FLAG_FAIL) != 0) { - throw new IOException("Invalid Huffman encoding"); - } - if ((flags & FLAG_EMIT) != 0) { - if (pos >= buf.length) { - buf = Arrays.copyOf(buf, buf.length * 2); - } - buf[pos++] = (byte) DECODE_TABLE[index][2]; - } - // Final validity depends only on last nibble's state + pos = processNibble(validateName, buf, pos, index, flags); accepted = (flags & FLAG_ACCEPTED) != 0; } @@ -675,7 +676,24 @@ static String decode(byte[] data, int offset, int length) throws IOException { throw new IOException("Invalid Huffman encoding: incomplete sequence"); } - return new String(buf, 0, pos, StandardCharsets.ISO_8859_1); + return pos; + } + + private static int processNibble(boolean validateName, byte[] buf, int pos, int index, int flags) + throws IOException { + if ((flags & FLAG_FAIL) != 0) { + throw new IOException("Invalid Huffman encoding"); + } + + if ((flags & FLAG_EMIT) != 0) { + byte emitted = (byte) DECODE_TABLE[index][2]; + if (validateName && emitted >= 'A' && emitted <= 'Z') { + throw new IOException("Header name contains uppercase"); + } + buf[pos++] = emitted; + } + + return pos; } private static int[][] buildDecodeTable() { diff --git a/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/StaticTable.java b/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/StaticTable.java new file mode 100644 index 000000000..dcffd1b66 --- /dev/null +++ b/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/StaticTable.java @@ -0,0 +1,210 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.hpack; + +import software.amazon.smithy.java.http.api.HeaderNames; + +/** + * HPACK static table from RFC 7541 Appendix A. + * + *

        The static table consists of 61 predefined header field entries, where index 0 is unused. + * + *

        This implementation uses length-based bucketing for fast lookups with zero per-lookup + * allocations. Entries are grouped by header name length, so lookups only scan candidates + * with matching name length (typically 1-3 entries per bucket). + * + *

        Header names use constants from {@link HeaderNames} to enable pointer comparisons. + */ +final class StaticTable { + + private StaticTable() {} + + /** + * Number of entries in the static table. + */ + static final int SIZE = 61; + + private static final String[] NAMES = new String[SIZE + 1]; + private static final String[] VALUES = new String[SIZE + 1]; + + private static void entry(int index, String name, String value) { + NAMES[index] = name; + VALUES[index] = value; + } + + static { + // RFC 7541 Appendix A - Static Table Definition + entry(1, HeaderNames.PSEUDO_AUTHORITY, ""); + entry(2, HeaderNames.PSEUDO_METHOD, "GET"); + entry(3, HeaderNames.PSEUDO_METHOD, "POST"); + entry(4, HeaderNames.PSEUDO_PATH, "/"); + entry(5, HeaderNames.PSEUDO_PATH, "/index.html"); + entry(6, HeaderNames.PSEUDO_SCHEME, "http"); + entry(7, HeaderNames.PSEUDO_SCHEME, "https"); + entry(8, HeaderNames.PSEUDO_STATUS, "200"); + entry(9, HeaderNames.PSEUDO_STATUS, "204"); + entry(10, HeaderNames.PSEUDO_STATUS, "206"); + entry(11, HeaderNames.PSEUDO_STATUS, "304"); + entry(12, HeaderNames.PSEUDO_STATUS, "400"); + entry(13, HeaderNames.PSEUDO_STATUS, "404"); + entry(14, HeaderNames.PSEUDO_STATUS, "500"); + entry(15, HeaderNames.ACCEPT_CHARSET, ""); + entry(16, HeaderNames.ACCEPT_ENCODING, "gzip, deflate"); + entry(17, HeaderNames.ACCEPT_LANGUAGE, ""); + entry(18, HeaderNames.ACCEPT_RANGES, ""); + entry(19, HeaderNames.ACCEPT, ""); + entry(20, HeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, ""); + entry(21, HeaderNames.AGE, ""); + entry(22, HeaderNames.ALLOW, ""); + entry(23, HeaderNames.AUTHORIZATION, ""); + entry(24, HeaderNames.CACHE_CONTROL, ""); + entry(25, HeaderNames.CONTENT_DISPOSITION, ""); + entry(26, HeaderNames.CONTENT_ENCODING, ""); + entry(27, HeaderNames.CONTENT_LANGUAGE, ""); + entry(28, HeaderNames.CONTENT_LENGTH, ""); + entry(29, HeaderNames.CONTENT_LOCATION, ""); + entry(30, HeaderNames.CONTENT_RANGE, ""); + entry(31, HeaderNames.CONTENT_TYPE, ""); + entry(32, HeaderNames.COOKIE, ""); + entry(33, HeaderNames.DATE, ""); + entry(34, HeaderNames.ETAG, ""); + entry(35, HeaderNames.EXPECT, ""); + entry(36, HeaderNames.EXPIRES, ""); + entry(37, HeaderNames.FROM, ""); + entry(38, HeaderNames.HOST, ""); + entry(39, HeaderNames.IF_MATCH, ""); + entry(40, HeaderNames.IF_MODIFIED_SINCE, ""); + entry(41, HeaderNames.IF_NONE_MATCH, ""); + entry(42, HeaderNames.IF_RANGE, ""); + entry(43, HeaderNames.IF_UNMODIFIED_SINCE, ""); + entry(44, HeaderNames.LAST_MODIFIED, ""); + entry(45, HeaderNames.LINK, ""); + entry(46, HeaderNames.LOCATION, ""); + entry(47, HeaderNames.MAX_FORWARDS, ""); + entry(48, HeaderNames.PROXY_AUTHENTICATE, ""); + entry(49, HeaderNames.PROXY_AUTHORIZATION, ""); + entry(50, HeaderNames.RANGE, ""); + entry(51, HeaderNames.REFERER, ""); + entry(52, HeaderNames.REFRESH, ""); + entry(53, HeaderNames.RETRY_AFTER, ""); + entry(54, HeaderNames.SERVER, ""); + entry(55, HeaderNames.SET_COOKIE, ""); + entry(56, HeaderNames.STRICT_TRANSPORT_SECURITY, ""); + entry(57, HeaderNames.TRANSFER_ENCODING, ""); + entry(58, HeaderNames.USER_AGENT, ""); + entry(59, HeaderNames.VARY, ""); + entry(60, HeaderNames.VIA, ""); + entry(61, HeaderNames.WWW_AUTHENTICATE, ""); + } + + /** + * Maximum header name length in the static table. + */ + private static final int MAX_NAME_LEN; + + /** + * Empty bucket for lengths with no entries (avoids null checks in lookups). + */ + private static final int[] EMPTY_BUCKET = new int[0]; + + /** + * Buckets of static table indices grouped by header name length. + * NAME_BUCKETS_BY_LEN[len] contains indices of entries whose name has that length. + */ + private static final int[][] NAME_BUCKETS_BY_LEN; + + static { + // Build length-based buckets for fast lookup + int maxLen = 0; + for (int i = 1; i <= SIZE; i++) { + int len = NAMES[i].length(); + if (len > maxLen) { + maxLen = len; + } + } + MAX_NAME_LEN = maxLen; + + // Count entries per length + int[] counts = new int[MAX_NAME_LEN + 1]; + for (int i = 1; i <= SIZE; i++) { + counts[NAMES[i].length()]++; + } + + // Allocate buckets + int[][] buckets = new int[MAX_NAME_LEN + 1][]; + for (int len = 0; len <= MAX_NAME_LEN; len++) { + buckets[len] = counts[len] > 0 ? new int[counts[len]] : EMPTY_BUCKET; + } + + // Fill buckets + int[] pos = new int[MAX_NAME_LEN + 1]; + for (int i = 1; i <= SIZE; i++) { + int len = NAMES[i].length(); + buckets[len][pos[len]++] = i; + } + + NAME_BUCKETS_BY_LEN = buckets; + } + + /** + * Get the header name at the given index. + * + * @param index 1-based index into static table + * @return header name + */ + static String getName(int index) { + return NAMES[index]; + } + + /** + * Get the header value at the given index. + * + * @param index 1-based index into static table + * @return header value + */ + static String getValue(int index) { + return VALUES[index]; + } + + /** + * Find index for a full match (name + value). + * + * @param name header name + * @param value header value + * @return index if found, -1 otherwise + */ + static int findFullMatch(String name, String value) { + int len = name.length(); + if (len > MAX_NAME_LEN) { + return -1; + } + for (int idx : NAME_BUCKETS_BY_LEN[len]) { + String entryName = NAMES[idx]; + if (name.equals(entryName) && value.equals(VALUES[idx])) { + return idx; + } + } + return -1; + } + + /** + * Find index for a name-only match. + * + * @param name header name + * @return index of first entry with this name, -1 if not found + */ + static int findNameMatch(String name) { + int len = name.length(); + if (len <= MAX_NAME_LEN) { + for (int idx : NAME_BUCKETS_BY_LEN[len]) { + if (name.equals(NAMES[idx])) { + return idx; + } + } + } + return -1; + } +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTableTest.java b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/DynamicTableTest.java similarity index 91% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTableTest.java rename to http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/DynamicTableTest.java index a0cf0d442..edd018226 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/DynamicTableTest.java +++ b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/DynamicTableTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.h2.hpack; +package software.amazon.smithy.java.http.hpack; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -19,8 +19,8 @@ void addsEntry() { table.add("name", "value"); assertEquals(1, table.length()); - assertEquals("name", table.get(62).name()); - assertEquals("value", table.get(62).value()); + assertEquals("name", table.getName(62)); + assertEquals("value", table.getValue(62)); } @Test @@ -54,8 +54,8 @@ void evictsOldestWhenFull() { // Would be 127, exceeds 100, so evict oldest assertEquals(2, table.length()); - assertEquals("third", table.get(62).name()); - assertEquals("second", table.get(63).name()); + assertEquals("third", table.getName(62)); + assertEquals("second", table.getName(63)); } @Test @@ -80,7 +80,7 @@ void setMaxSizeEvicts() { table.setMaxSize(50); // Only room for 1 entry assertEquals(1, table.length()); - assertEquals("name2", table.get(62).name()); + assertEquals("name2", table.getName(62)); } @Test @@ -98,8 +98,8 @@ void getThrowsOnInvalidIndex() { var table = new DynamicTable(4096); table.add("name", "value"); - assertThrows(IndexOutOfBoundsException.class, () -> table.get(61)); // Below dynamic range - assertThrows(IndexOutOfBoundsException.class, () -> table.get(63)); // Only 1 entry at 62 + assertThrows(IndexOutOfBoundsException.class, () -> table.getName(61)); // Below dynamic range + assertThrows(IndexOutOfBoundsException.class, () -> table.getName(63)); // Only 1 entry at 62 } @Test diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoderTest.java b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HpackDecoderTest.java similarity index 75% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoderTest.java rename to http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HpackDecoderTest.java index 00bb6ce64..5f2b95065 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackDecoderTest.java +++ b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HpackDecoderTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.h2.hpack; +package software.amazon.smithy.java.http.hpack; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -31,11 +31,11 @@ void decodesIndexedNameFromDynamicTable() throws IOException { var out2 = new ByteArrayOutputStream(); encoder.beginHeaderBlock(out2); encoder.encodeHeader(out2, "x-custom-name", "value2", false); - List headers = decoder.decode(out2.toByteArray()); + List headers = decoder.decode(out2.toByteArray()); - assertEquals(1, headers.size()); - assertEquals("x-custom-name", headers.getFirst().name()); - assertEquals("value2", headers.getFirst().value()); + assertEquals(2, headers.size()); // name, value + assertEquals("x-custom-name", headers.get(0)); + assertEquals("value2", headers.get(1)); } @Test @@ -81,11 +81,6 @@ void throwsOnHeaderListExceedsMaxSize() { @Test void throwsOnUppercaseHeaderName() { // Craft: literal without indexing (0x00), name length 4, "Test" (uppercase T) - // 0x00 = literal without indexing, name index 0 - // 0x04 = string length 4, no huffman - // "Test" = uppercase T - // 0x05 = value length 5 - // "value" byte[] malformed = {0x00, 0x04, 'T', 'e', 's', 't', 0x05, 'v', 'a', 'l', 'u', 'e'}; var decoder = new HpackDecoder(4096); @@ -95,32 +90,26 @@ void throwsOnUppercaseHeaderName() { @Test void allowsTableSizeUpdateAtBeginning() throws IOException { - // Table size update (0x3f 0x01 = size 32) followed by indexed header - // 0x20 | 0x1f = 0x3f means size >= 31, next byte 0x01 means size = 31 + 1 = 32 - // Actually simpler: 0x20 = table size 0 (just 0x20 with 5-bit prefix) - byte[] valid = {0x20, (byte) 0x82}; // table size 0, then :method GET + // Table size update (0x20 = size 0) followed by indexed header (0x82 = :method GET) + byte[] valid = {0x20, (byte) 0x82}; var decoder = new HpackDecoder(4096); - List headers = decoder.decode(valid); + List headers = decoder.decode(valid); - assertEquals(1, headers.size()); - assertEquals(":method", headers.getFirst().name()); - assertEquals("GET", headers.getFirst().value()); + assertEquals(2, headers.size()); + assertEquals(":method", headers.get(0)); + assertEquals("GET", headers.get(1)); } @Test void decodesLiteralNeverIndexed() throws IOException { // 0x10 = literal never indexed, name index 0 - // 0x04 = name length 4 - // "test" - // 0x05 = value length 5 - // "value" byte[] data = {0x10, 0x04, 't', 'e', 's', 't', 0x05, 'v', 'a', 'l', 'u', 'e'}; var decoder = new HpackDecoder(4096); - List headers = decoder.decode(data); + List headers = decoder.decode(data); - assertEquals(1, headers.size()); - assertEquals("test", headers.getFirst().name()); - assertEquals("value", headers.getFirst().value()); + assertEquals(2, headers.size()); + assertEquals("test", headers.get(0)); + assertEquals("value", headers.get(1)); } @Test @@ -128,11 +117,11 @@ void decodesLiteralWithoutIndexing() throws IOException { // 0x00 = literal without indexing, name index 0 byte[] data = {0x00, 0x04, 't', 'e', 's', 't', 0x05, 'v', 'a', 'l', 'u', 'e'}; var decoder = new HpackDecoder(4096); - List headers = decoder.decode(data); + List headers = decoder.decode(data); - assertEquals(1, headers.size()); - assertEquals("test", headers.getFirst().name()); - assertEquals("value", headers.getFirst().value()); + assertEquals(2, headers.size()); + assertEquals("test", headers.get(0)); + assertEquals("value", headers.get(1)); } @Test diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoderTest.java b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HpackEncoderTest.java similarity index 72% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoderTest.java rename to http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HpackEncoderTest.java index 1fabd1c8f..fa69b1202 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackEncoderTest.java +++ b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HpackEncoderTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.h2.hpack; +package software.amazon.smithy.java.http.hpack; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -23,11 +23,11 @@ void encodesStaticIndexedHeader() throws IOException { encoder.encodeHeader(out, ":method", "GET", false); var decoder = new HpackDecoder(4096); - List headers = decoder.decode(out.toByteArray()); + List headers = decoder.decode(out.toByteArray()); - assertEquals(1, headers.size()); - assertEquals(":method", headers.getFirst().name()); - assertEquals("GET", headers.getFirst().value()); + assertEquals(2, headers.size()); + assertEquals(":method", headers.get(0)); + assertEquals("GET", headers.get(1)); } @Test @@ -38,11 +38,11 @@ void encodesLiteralWithIndexing() throws IOException { encoder.encodeHeader(out, "x-custom", "value", false); var decoder = new HpackDecoder(4096); - List headers = decoder.decode(out.toByteArray()); + List headers = decoder.decode(out.toByteArray()); - assertEquals(1, headers.size()); - assertEquals("x-custom", headers.getFirst().name()); - assertEquals("value", headers.getFirst().value()); + assertEquals(2, headers.size()); + assertEquals("x-custom", headers.get(0)); + assertEquals("value", headers.get(1)); } @Test @@ -55,15 +55,15 @@ void encodesMultipleHeaders() throws IOException { encoder.encodeHeader(out, ":scheme", "https", false); var decoder = new HpackDecoder(4096); - List headers = decoder.decode(out.toByteArray()); - - assertEquals(3, headers.size()); - assertEquals(":method", headers.get(0).name()); - assertEquals("GET", headers.get(0).value()); - assertEquals(":path", headers.get(1).name()); - assertEquals("/", headers.get(1).value()); - assertEquals(":scheme", headers.get(2).name()); - assertEquals("https", headers.get(2).value()); + List headers = decoder.decode(out.toByteArray()); + + assertEquals(6, headers.size()); // 3 headers * 2 + assertEquals(":method", headers.get(0)); + assertEquals("GET", headers.get(1)); + assertEquals(":path", headers.get(2)); + assertEquals("/", headers.get(3)); + assertEquals(":scheme", headers.get(4)); + assertEquals("https", headers.get(5)); } @Test @@ -82,12 +82,12 @@ void reusesDynamicTableEntry() throws IOException { var out2 = new ByteArrayOutputStream(); encoder.beginHeaderBlock(out2); encoder.encodeHeader(out2, "x-custom", "value", false); - List headers = decoder.decode(out2.toByteArray()); + List headers = decoder.decode(out2.toByteArray()); int secondSize = out2.size(); - assertEquals(1, headers.size()); - assertEquals("x-custom", headers.getFirst().name()); - assertEquals("value", headers.getFirst().value()); + assertEquals(2, headers.size()); + assertEquals("x-custom", headers.get(0)); + assertEquals("value", headers.get(1)); // Second encoding should be smaller (indexed reference) assertTrue(secondSize < firstSize); } @@ -107,10 +107,10 @@ void encodesSensitiveHeaderNeverIndexed() throws IOException { var out2 = new ByteArrayOutputStream(); encoder.beginHeaderBlock(out2); encoder.encodeHeader(out2, "x-secret", "password", true); - List headers = decoder.decode(out2.toByteArray()); + List headers = decoder.decode(out2.toByteArray()); - assertEquals(1, headers.size()); - assertEquals("x-secret", headers.getFirst().name()); + assertEquals(2, headers.size()); + assertEquals("x-secret", headers.get(0)); // Size should be same (not indexed) assertEquals(out1.size(), out2.size()); } @@ -130,10 +130,10 @@ void authorizationHeaderNeverIndexed() throws IOException { var out2 = new ByteArrayOutputStream(); encoder.beginHeaderBlock(out2); encoder.encodeHeader(out2, "authorization", "Bearer token", false); - List headers = decoder.decode(out2.toByteArray()); + List headers = decoder.decode(out2.toByteArray()); - assertEquals(1, headers.size()); - assertEquals("authorization", headers.getFirst().name()); + assertEquals(2, headers.size()); + assertEquals("authorization", headers.get(0)); // Size should be same (not indexed) assertEquals(out1.size(), out2.size()); } @@ -146,11 +146,11 @@ void encodesWithoutHuffman() throws IOException { encoder.encodeHeader(out, "x-test", "hello", false); var decoder = new HpackDecoder(4096); - List headers = decoder.decode(out.toByteArray()); + List headers = decoder.decode(out.toByteArray()); - assertEquals(1, headers.size()); - assertEquals("x-test", headers.getFirst().name()); - assertEquals("hello", headers.getFirst().value()); + assertEquals(2, headers.size()); + assertEquals("x-test", headers.get(0)); + assertEquals("hello", headers.get(1)); } @Test @@ -166,10 +166,10 @@ void emitsTableSizeUpdate() throws IOException { encoder.encodeHeader(out, ":method", "GET", false); // Decoder should handle the table size update - List headers = decoder.decode(out.toByteArray()); + List headers = decoder.decode(out.toByteArray()); - assertEquals(1, headers.size()); - assertEquals(":method", headers.getFirst().name()); + assertEquals(2, headers.size()); + assertEquals(":method", headers.get(0)); } @Test @@ -207,11 +207,11 @@ void encodesLargeInteger() throws IOException { encoder.encodeHeader(out, "x-long", longValue, false); var decoder = new HpackDecoder(4096); - List headers = decoder.decode(out.toByteArray()); + List headers = decoder.decode(out.toByteArray()); - assertEquals(1, headers.size()); - assertEquals("x-long", headers.getFirst().name()); - assertEquals(longValue, headers.getFirst().value()); + assertEquals(2, headers.size()); + assertEquals("x-long", headers.get(0)); + assertEquals(longValue, headers.get(1)); } @Test @@ -223,10 +223,10 @@ void encodesStaticNameWithNewValue() throws IOException { encoder.encodeHeader(out, ":path", "/custom/path", false); var decoder = new HpackDecoder(4096); - List headers = decoder.decode(out.toByteArray()); + List headers = decoder.decode(out.toByteArray()); - assertEquals(1, headers.size()); - assertEquals(":path", headers.getFirst().name()); - assertEquals("/custom/path", headers.getFirst().value()); + assertEquals(2, headers.size()); + assertEquals(":path", headers.get(0)); + assertEquals("/custom/path", headers.get(1)); } } diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackTestSuiteTest.java b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HpackTestSuiteTest.java similarity index 90% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackTestSuiteTest.java rename to http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HpackTestSuiteTest.java index ef72cbeee..a952eeb90 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HpackTestSuiteTest.java +++ b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HpackTestSuiteTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.h2.hpack; +package software.amazon.smithy.java.http.hpack; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -81,22 +81,23 @@ void decodeTestCase(String filename, int seqno, String wireHex, List e // Decode this case's wire bytes byte[] wireBytes = hexToBytes(wireHex); - List result = decoder.decode(wireBytes); + List result = decoder.decode(wireBytes); - // Verify the decoded headers match expected + // Verify the decoded headers match expected (result is flat: name0, value0, name1, value1, ...) assertEquals(expectedHeaders.size(), - result.size(), + result.size() / 2, "Header count mismatch for " + filename + " case " + seqno); for (int i = 0; i < expectedHeaders.size(); i++) { String[] expected = expectedHeaders.get(i); - HeaderField actual = result.get(i); + String actualName = result.get(i * 2); + String actualValue = result.get(i * 2 + 1); assertEquals(expected[0], - actual.name(), + actualName, "Header name mismatch at index " + i + " for " + filename + " case " + seqno); assertEquals(expected[1], - actual.value(), + actualValue, "Header value mismatch at index " + i + " for " + filename + " case " + seqno); } } diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HuffmanTest.java b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HuffmanTest.java similarity index 97% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HuffmanTest.java rename to http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HuffmanTest.java index 29db5f97c..20fb97957 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/HuffmanTest.java +++ b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HuffmanTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.h2.hpack; +package software.amazon.smithy.java.http.hpack; import static org.junit.jupiter.api.Assertions.*; diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTableTest.java b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/StaticTableTest.java similarity index 92% rename from http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTableTest.java rename to http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/StaticTableTest.java index 808b21c7c..42dcd0a3c 100644 --- a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/hpack/StaticTableTest.java +++ b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/StaticTableTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.http.client.h2.hpack; +package software.amazon.smithy.java.http.hpack; import static org.junit.jupiter.api.Assertions.*; @@ -24,9 +24,8 @@ void sizeIs61() { @ParameterizedTest(name = "index {0}: {1}={2}") @MethodSource("staticTableEntries") void getReturnsCorrectEntry(int index, String expectedName, String expectedValue) { - HeaderField field = StaticTable.get(index); - assertEquals(expectedName, field.name()); - assertEquals(expectedValue, field.value()); + assertEquals(expectedName, StaticTable.getName(index)); + assertEquals(expectedValue, StaticTable.getValue(index)); } static Stream staticTableEntries() { @@ -65,7 +64,6 @@ static Stream fullMatchCases() { Arguments.of(":status", "200", 8), Arguments.of(":status", "404", 13), Arguments.of("accept-encoding", "gzip, deflate", 16), - // No match cases Arguments.of(":method", "PUT", -1), Arguments.of(":status", "201", -1), Arguments.of("x-custom", "value", -1), @@ -90,14 +88,12 @@ static Stream nameMatchCases() { Arguments.of("content-type", 31), Arguments.of("host", 38), Arguments.of("www-authenticate", 61), - // No match Arguments.of("x-custom", -1), Arguments.of("x-request-id", -1)); } @Test void findFullMatchWithHeaderNamesConstant() { - // Using HeaderNames constant should enable pointer comparison optimization assertEquals(2, StaticTable.findFullMatch(HeaderNames.PSEUDO_METHOD, "GET")); assertEquals(8, StaticTable.findFullMatch(HeaderNames.PSEUDO_STATUS, "200")); } diff --git a/http/http-client/src/test/resources/hpack-test-case/LICENSE b/http/http-hpack/src/test/resources/hpack-test-case/LICENSE similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/LICENSE rename to http/http-hpack/src/test/resources/hpack-test-case/LICENSE diff --git a/http/http-client/src/test/resources/hpack-test-case/story_00.json b/http/http-hpack/src/test/resources/hpack-test-case/story_00.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_00.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_00.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_01.json b/http/http-hpack/src/test/resources/hpack-test-case/story_01.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_01.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_01.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_02.json b/http/http-hpack/src/test/resources/hpack-test-case/story_02.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_02.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_02.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_03.json b/http/http-hpack/src/test/resources/hpack-test-case/story_03.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_03.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_03.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_04.json b/http/http-hpack/src/test/resources/hpack-test-case/story_04.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_04.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_04.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_05.json b/http/http-hpack/src/test/resources/hpack-test-case/story_05.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_05.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_05.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_06.json b/http/http-hpack/src/test/resources/hpack-test-case/story_06.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_06.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_06.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_07.json b/http/http-hpack/src/test/resources/hpack-test-case/story_07.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_07.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_07.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_08.json b/http/http-hpack/src/test/resources/hpack-test-case/story_08.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_08.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_08.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_09.json b/http/http-hpack/src/test/resources/hpack-test-case/story_09.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_09.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_09.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_10.json b/http/http-hpack/src/test/resources/hpack-test-case/story_10.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_10.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_10.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_11.json b/http/http-hpack/src/test/resources/hpack-test-case/story_11.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_11.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_11.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_12.json b/http/http-hpack/src/test/resources/hpack-test-case/story_12.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_12.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_12.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_13.json b/http/http-hpack/src/test/resources/hpack-test-case/story_13.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_13.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_13.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_14.json b/http/http-hpack/src/test/resources/hpack-test-case/story_14.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_14.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_14.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_15.json b/http/http-hpack/src/test/resources/hpack-test-case/story_15.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_15.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_15.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_16.json b/http/http-hpack/src/test/resources/hpack-test-case/story_16.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_16.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_16.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_17.json b/http/http-hpack/src/test/resources/hpack-test-case/story_17.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_17.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_17.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_18.json b/http/http-hpack/src/test/resources/hpack-test-case/story_18.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_18.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_18.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_19.json b/http/http-hpack/src/test/resources/hpack-test-case/story_19.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_19.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_19.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_20.json b/http/http-hpack/src/test/resources/hpack-test-case/story_20.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_20.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_20.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_21.json b/http/http-hpack/src/test/resources/hpack-test-case/story_21.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_21.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_21.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_22.json b/http/http-hpack/src/test/resources/hpack-test-case/story_22.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_22.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_22.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_23.json b/http/http-hpack/src/test/resources/hpack-test-case/story_23.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_23.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_23.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_24.json b/http/http-hpack/src/test/resources/hpack-test-case/story_24.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_24.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_24.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_25.json b/http/http-hpack/src/test/resources/hpack-test-case/story_25.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_25.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_25.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_26.json b/http/http-hpack/src/test/resources/hpack-test-case/story_26.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_26.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_26.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_27.json b/http/http-hpack/src/test/resources/hpack-test-case/story_27.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_27.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_27.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_28.json b/http/http-hpack/src/test/resources/hpack-test-case/story_28.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_28.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_28.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_29.json b/http/http-hpack/src/test/resources/hpack-test-case/story_29.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_29.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_29.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_30.json b/http/http-hpack/src/test/resources/hpack-test-case/story_30.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_30.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_30.json diff --git a/http/http-client/src/test/resources/hpack-test-case/story_31.json b/http/http-hpack/src/test/resources/hpack-test-case/story_31.json similarity index 100% rename from http/http-client/src/test/resources/hpack-test-case/story_31.json rename to http/http-hpack/src/test/resources/hpack-test-case/story_31.json diff --git a/settings.gradle.kts b/settings.gradle.kts index fc1714a8f..b0d0d0927 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,6 +28,7 @@ include(":framework-errors") include(":http:http-api") include(":http:http-binding") include(":http:http-client") +include(":http:http-hpack") include(":retries-api") include(":tracing-api") From 5abd643e86bbff37e521b4fa474deca60190e662 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Fri, 13 Feb 2026 14:35:59 -0600 Subject: [PATCH 57/60] Fuzz test HPACK, Huffman, chunked & H2 frame codec --- .github/workflows/fuzz-testing.yml | 11 ++ http/http-client/build.gradle.kts | 8 + .../connection/H2ConnectionManager.java | 3 +- .../http/client/h1/ChunkedInputStream.java | 14 +- .../client/h1/ChunkedEncodingFuzzTest.java | 65 ++++++++ .../http/client/h2/H2FrameCodecFuzzTest.java | 44 +++++ http/http-hpack/build.gradle.kts | 11 ++ .../smithy/java/http/hpack/HpackDecoder.java | 14 +- .../java/http/hpack/HpackDecoderFuzzTest.java | 155 ++++++++++++++++++ .../java/http/hpack/HuffmanFuzzTest.java | 58 +++++++ 10 files changed, 374 insertions(+), 9 deletions(-) create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedEncodingFuzzTest.java create mode 100644 http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameCodecFuzzTest.java create mode 100644 http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HpackDecoderFuzzTest.java create mode 100644 http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HuffmanFuzzTest.java diff --git a/.github/workflows/fuzz-testing.yml b/.github/workflows/fuzz-testing.yml index a29a3b88e..6e362e62a 100644 --- a/.github/workflows/fuzz-testing.yml +++ b/.github/workflows/fuzz-testing.yml @@ -59,6 +59,17 @@ jobs: run: | ./gradlew :client:client-rulesengine:test --tests "*FuzzTest*" -Djazzer.max_duration=1h --stacktrace + - name: Run HPACK fuzz tests + env: + JAZZER_FUZZ: "1" + run: | + ./gradlew :http:http-hpack:test --tests "*FuzzTest*" -Djazzer.max_duration=1h --stacktrace + + - name: Run HTTP client fuzz tests + env: + JAZZER_FUZZ: "1" + run: | + ./gradlew :http:http-client:test --tests "*FuzzTest*" -Djazzer.max_duration=1h --stacktrace - name: Save fuzz corpus cache uses: actions/cache/save@v5 if: always() diff --git a/http/http-client/build.gradle.kts b/http/http-client/build.gradle.kts index a2846ccd0..ce189c94f 100644 --- a/http/http-client/build.gradle.kts +++ b/http/http-client/build.gradle.kts @@ -32,6 +32,10 @@ dependencies { // Jackson for HPACK test suite JSON parsing testImplementation("com.fasterxml.jackson.core:jackson-databind:2.18.2") + // Jazzer for fuzz testing + testImplementation(libs.jazzer.junit) + testImplementation(libs.jazzer.api) + // Add Apache HttpClient for benchmarking comparison jmh("org.apache.httpcomponents.client5:httpclient5:5.3.1") @@ -150,3 +154,7 @@ tasks.named("jmh") { dependsOn(startBenchmarkServer) finalizedBy(stopBenchmarkServer) } + +tasks.test { + maxHeapSize = "2g" +} diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java index d10e70f43..23450413a 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/connection/H2ConnectionManager.java @@ -126,7 +126,8 @@ H2Connection acquire(Route route, int maxConnectionsForRoute) throws IOException } boolean canExpand = totalConns < maxConnectionsForRoute; - int selected = loadBalancer.select(state.activeStreamsBuf, connCount, + int selected = loadBalancer.select(state.activeStreamsBuf, + connCount, canExpand ? maxConnectionsForRoute : connCount); if (selected >= 0) { diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java index 4dc468f37..c3649193a 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/h1/ChunkedInputStream.java @@ -248,12 +248,16 @@ private static long parseHex(byte[] buf, int start, int end) throws IOException private void readTrailers() throws IOException { ModifiableHttpHeaders parsedTrailers = HttpHeaders.ofModifiable(); int len; - while ((len = delegate.readLine(lineBuffer, MAX_LINE_LENGTH)) > 0) { - String name = H1Utils.parseHeaderLine(lineBuffer, len, parsedTrailers); - if (name == null) { - throw new IOException("Invalid trailer line: " - + new String(lineBuffer, 0, len, StandardCharsets.US_ASCII)); + try { + while ((len = delegate.readLine(lineBuffer, MAX_LINE_LENGTH)) > 0) { + String name = H1Utils.parseHeaderLine(lineBuffer, len, parsedTrailers); + if (name == null) { + throw new IOException("Invalid trailer line: " + + new String(lineBuffer, 0, len, StandardCharsets.US_ASCII)); + } } + } catch (IllegalArgumentException e) { + throw new IOException("Invalid trailer header", e); } // Only store if we actually got trailers diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedEncodingFuzzTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedEncodingFuzzTest.java new file mode 100644 index 000000000..57b30997a --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h1/ChunkedEncodingFuzzTest.java @@ -0,0 +1,65 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h1; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import com.code_intelligence.jazzer.junit.FuzzTest; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import software.amazon.smithy.java.http.client.UnsyncBufferedInputStream; +import software.amazon.smithy.java.http.client.UnsyncBufferedOutputStream; + +/** + * Fuzz tests for HTTP/1.1 chunked transfer encoding. + */ +class ChunkedEncodingFuzzTest { + + private static final int MAX_FUZZ_INPUT = 512; + + // --- Crash safety: random bytes as chunked input --- + + @FuzzTest + void fuzzChunkedInputDecode(byte[] data) { + if (data.length > MAX_FUZZ_INPUT) { + return; + } + var bis = new UnsyncBufferedInputStream(new ByteArrayInputStream(data), 1024); + var chunked = new ChunkedInputStream(bis); + try { + chunked.transferTo(OutputStream.nullOutputStream()); + } catch (IOException ignored) {} + } + + // --- Round-trip: write chunked → read chunked → verify identity --- + + @FuzzTest + void fuzzChunkedRoundTrip(byte[] data) throws IOException { + if (data.length > MAX_FUZZ_INPUT) { + return; + } + + // Write through ChunkedOutputStream + var wireBuffer = new ByteArrayOutputStream(); + var bos = new UnsyncBufferedOutputStream(wireBuffer, 1024); + var chunkedOut = new ChunkedOutputStream(bos, 64); + chunkedOut.write(data); + chunkedOut.close(); + + // Read back through ChunkedInputStream + byte[] wire = wireBuffer.toByteArray(); + var bis = new UnsyncBufferedInputStream(new ByteArrayInputStream(wire), 1024); + var chunkedIn = new ChunkedInputStream(bis); + var result = new ByteArrayOutputStream(); + chunkedIn.transferTo(result); + byte[] decoded = result.toByteArray(); + + assertArrayEquals(data, decoded, "Chunked round-trip mismatch"); + } + +} diff --git a/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameCodecFuzzTest.java b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameCodecFuzzTest.java new file mode 100644 index 000000000..93b6b625e --- /dev/null +++ b/http/http-client/src/test/java/software/amazon/smithy/java/http/client/h2/H2FrameCodecFuzzTest.java @@ -0,0 +1,44 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.client.h2; + +import com.code_intelligence.jazzer.junit.FuzzTest; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import software.amazon.smithy.java.http.client.UnsyncBufferedInputStream; +import software.amazon.smithy.java.http.client.UnsyncBufferedOutputStream; + +/** + * Fuzz test for H2 frame codec — feeds random bytes as a stream of H2 frames. + */ +class H2FrameCodecFuzzTest { + + private static final int MAX_FUZZ_INPUT = 512; + + @FuzzTest + void fuzzFrameStream(byte[] data) { + if (data.length > MAX_FUZZ_INPUT) { + return; + } + var codec = new H2FrameCodec( + new UnsyncBufferedInputStream(new ByteArrayInputStream(data), 1024), + new UnsyncBufferedOutputStream(new ByteArrayOutputStream(), 1024), + 16384); + for (int i = 0; i < 10; i++) { + try { + codec.nextFrame(); + int length = codec.framePayloadLength(); + if (length > 0) { + byte[] payload = new byte[length]; + codec.readPayloadInto(payload, 0, length); + } + } catch (IOException ignored) { + break; + } + } + } +} diff --git a/http/http-hpack/build.gradle.kts b/http/http-hpack/build.gradle.kts index 6757e09f5..0d63a02c9 100644 --- a/http/http-hpack/build.gradle.kts +++ b/http/http-hpack/build.gradle.kts @@ -12,4 +12,15 @@ dependencies { // Jackson for HPACK test suite JSON parsing testImplementation("com.fasterxml.jackson.core:jackson-databind:2.18.2") + + // Jazzer for fuzz testing + testImplementation(libs.jazzer.junit) + testImplementation(libs.jazzer.api) + + // Netty HPACK for differential fuzz testing + testImplementation("io.netty:netty-codec-http2:4.2.7.Final") +} + +tasks.test { + maxHeapSize = "2g" } diff --git a/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/HpackDecoder.java b/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/HpackDecoder.java index 4ff5fad39..15e1b0c11 100644 --- a/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/HpackDecoder.java +++ b/http/http-hpack/src/main/java/software/amazon/smithy/java/http/hpack/HpackDecoder.java @@ -114,8 +114,12 @@ public List decode(byte[] data, int offset, int length) throws IOExcepti name = StaticTable.getName(index); value = StaticTable.getValue(index); } else { - name = dynamicTable.getName(index); - value = dynamicTable.getValue(index); + try { + name = dynamicTable.getName(index); + value = dynamicTable.getValue(index); + } catch (IndexOutOfBoundsException e) { + throw new IOException(e.getMessage(), e); + } } headerFieldSeen = true; } else if ((b & 0x40) != 0) { @@ -171,7 +175,11 @@ private String getIndexedName(int index) throws IOException { if (index <= 0) { throw new IOException("Invalid HPACK name index: " + index); } - return index <= StaticTable.SIZE ? StaticTable.getName(index) : dynamicTable.getName(index); + try { + return index <= StaticTable.SIZE ? StaticTable.getName(index) : dynamicTable.getName(index); + } catch (IndexOutOfBoundsException e) { + throw new IOException(e.getMessage(), e); + } } /** diff --git a/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HpackDecoderFuzzTest.java b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HpackDecoderFuzzTest.java new file mode 100644 index 000000000..6c610fa08 --- /dev/null +++ b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HpackDecoderFuzzTest.java @@ -0,0 +1,155 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.hpack; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.code_intelligence.jazzer.junit.FuzzTest; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http2.DefaultHttp2HeadersDecoder; +import io.netty.handler.codec.http2.Http2Headers; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Fuzz tests for HPACK decoder. + * + *

        Tests crash safety, differential correctness against Netty, + * property-based invariants, resource limits, and state machine consistency. + */ +class HpackDecoderFuzzTest { + + // --- Crash safety --- + + private static final int MAX_FUZZ_INPUT = 512; + + @FuzzTest(maxDuration = "5m") + void fuzzDecode(byte[] data) { + if (data.length > MAX_FUZZ_INPUT) { + return; + } + try { + new HpackDecoder(4096).decode(data); + } catch (IOException ignored) {} + } + + // --- Differential testing against Netty --- + + @FuzzTest + void fuzzDifferentialVsNetty(byte[] data) { + if (data.length > MAX_FUZZ_INPUT) { + return; + } + // Decode with our implementation + var smithyDecoder = new HpackDecoder(4096); + List smithyResult = null; + Exception smithyError = null; + try { + smithyResult = smithyDecoder.decode(data); + } catch (Exception e) { + smithyError = e; + } + + // Decode with Netty + var nettyDecoder = new DefaultHttp2HeadersDecoder(); + Http2Headers nettyResult = null; + Exception nettyError = null; + try { + nettyResult = nettyDecoder.decodeHeaders(1, Unpooled.wrappedBuffer(data)); + } catch (Exception e) { + nettyError = e; + } + + // Both should agree on success/failure + if (smithyError != null && nettyError != null) { + return; // Both rejected — fine + } + if (smithyError == null && nettyError == null) { + // Both succeeded — compare results + int smithyCount = smithyResult.size() / 2; + int nettyCount = nettyResult.size(); + // Netty includes pseudo-headers in the count differently, so just verify + // we decoded the same number of headers + assertEquals(nettyCount, + smithyCount, + "Header count mismatch: smithy=" + smithyCount + " netty=" + nettyCount); + } + // One succeeded and one failed — this is acceptable because implementations + // may differ on strictness (e.g., header validation, size limits) + } + + // --- Property-based invariants: encode → decode round-trip --- + + @FuzzTest + void fuzzRoundTrip(byte[] data) throws IOException { + if (data.length > MAX_FUZZ_INPUT) { + return; + } + if (data.length < 4) { + return; + } + + // Use fuzz data to generate header name/value pairs + var headers = extractHeaders(data); + if (headers.isEmpty()) { + return; + } + + // Encode with our encoder + var encoder = new HpackEncoder(4096); + var out = new ByteArrayOutputStream(); + encoder.beginHeaderBlock(out); + for (int i = 0; i < headers.size(); i += 2) { + encoder.encodeHeader(out, headers.get(i), headers.get(i + 1), false); + } + byte[] encoded = out.toByteArray(); + + // Decode and verify + var decoder = new HpackDecoder(4096); + List decoded = decoder.decode(encoded); + + assertEquals(headers.size(), decoded.size(), "Round-trip header count mismatch"); + for (int i = 0; i < headers.size(); i += 2) { + assertEquals(headers.get(i), decoded.get(i), "Name mismatch at " + (i / 2)); + assertEquals(headers.get(i + 1), decoded.get(i + 1), "Value mismatch at " + (i / 2)); + } + } + + /** + * Extract lowercase header name/value pairs from fuzz data. + */ + private static List extractHeaders(byte[] data) { + var headers = new ArrayList(); + int pos = 0; + while (pos + 2 < data.length && headers.size() < 20) { + int nameLen = (data[pos] & 0x0F) + 1; // 1-16 + pos++; + if (pos + nameLen >= data.length) { + break; + } + // Build lowercase ASCII name + var name = new StringBuilder(nameLen); + for (int i = 0; i < nameLen; i++) { + name.append((char) ('a' + ((data[pos + i] & 0xFF) % 26))); + } + pos += nameLen; + + int valueLen = data[pos] & 0x3F; // 0-63 + pos++; + if (pos + valueLen > data.length) { + break; + } + var value = new String(data, pos, valueLen, java.nio.charset.StandardCharsets.ISO_8859_1); + pos += valueLen; + + headers.add(name.toString()); + headers.add(value); + } + return headers; + } +} diff --git a/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HuffmanFuzzTest.java b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HuffmanFuzzTest.java new file mode 100644 index 000000000..412919443 --- /dev/null +++ b/http/http-hpack/src/test/java/software/amazon/smithy/java/http/hpack/HuffmanFuzzTest.java @@ -0,0 +1,58 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.http.hpack; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.code_intelligence.jazzer.junit.FuzzTest; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Fuzz tests for Huffman codec. + */ +class HuffmanFuzzTest { + + private static final int MAX_FUZZ_INPUT = 1024; + + @FuzzTest + void fuzzDecode(byte[] data) { + if (data.length > MAX_FUZZ_INPUT) { + return; + } + try { + Huffman.decode(data, 0, data.length); + } catch (IOException ignored) {} + } + + @FuzzTest + void fuzzDecodeHeaderName(byte[] data) { + if (data.length > MAX_FUZZ_INPUT) { + return; + } + try { + Huffman.decodeHeaderName(data, 0, data.length); + } catch (IOException ignored) {} + } + + @FuzzTest + void fuzzEncode(byte[] data) throws IOException { + if (data.length > MAX_FUZZ_INPUT) { + return; + } + + // Verify encodedLength prediction matches actual encode + var out = new ByteArrayOutputStream(); + Huffman.encode(data, 0, data.length, out); + byte[] encoded = out.toByteArray(); + + int predictedLen = Huffman.encodedLength(data, 0, data.length); + assertEquals(encoded.length, + predictedLen, + "encodedLength prediction mismatch"); + } + +} From 90227d10b1761b9722c35abc4bce302b54cb508c Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Tue, 17 Feb 2026 10:33:17 -0600 Subject: [PATCH 58/60] Add writeTo DataStream optimization Avoids an intermediate InputStream in many cases. --- .../java/http/client/H2cScalingBenchmark.java | 2 - .../java/http/client/DefaultHttpClient.java | 2 +- .../io/datastream/ByteBufferDataStream.java | 7 ++ .../smithy/java/io/datastream/DataStream.java | 17 +++++ .../java/io/datastream/EmptyDataStream.java | 6 ++ .../io/datastream/InputStreamDataStream.java | 6 ++ .../io/datastream/PublisherDataStream.java | 72 +++++++++++++++++++ .../java/io/datastream/WrappedDataStream.java | 7 ++ .../datastream/ByteBufferDataStreamTest.java | 28 ++++++++ .../datastream/InputStreamDataStreamTest.java | 24 +++++++ .../datastream/PublisherDataStreamTest.java | 63 ++++++++++++++++ .../io/datastream/WrappedDataStreamTest.java | 15 ++++ 12 files changed, 246 insertions(+), 3 deletions(-) create mode 100644 io/src/test/java/software/amazon/smithy/java/io/datastream/PublisherDataStreamTest.java diff --git a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java index f6d37a621..2af36042a 100644 --- a/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java +++ b/http/http-client/src/jmh/java/software/amazon/smithy/java/http/client/H2cScalingBenchmark.java @@ -55,7 +55,6 @@ import org.openjdk.jmh.annotations.Warmup; import software.amazon.smithy.java.http.api.HttpRequest; import software.amazon.smithy.java.http.client.connection.ConnectionPoolListener; -import software.amazon.smithy.java.http.client.connection.H2LoadBalancer; import software.amazon.smithy.java.http.client.connection.HttpConnection; import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; @@ -121,7 +120,6 @@ public void setupIteration() throws Exception { .maxConnectionsPerRoute(connections) .maxTotalConnections(connections) .h2StreamsPerConnection(streamsPerConnection) - .h2LoadBalancer(H2LoadBalancer.watermark(1, streamsPerConnection)) .h2InitialWindowSize(1024 * 1024) .maxIdleTime(Duration.ofMinutes(2)) .httpVersionPolicy(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java index ec32e3b3a..3f068666d 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/DefaultHttpClient.java @@ -59,7 +59,7 @@ private HttpResponse sendInternal(HttpRequest request, RequestOptions options) t DataStream requestBody = effectiveRequest.body(); if (requestBody != null && requestBody.contentLength() != 0) { try (OutputStream out = exchange.requestBody()) { - requestBody.asInputStream().transferTo(out); + requestBody.writeTo(out); } } else { exchange.requestBody().close(); diff --git a/io/src/main/java/software/amazon/smithy/java/io/datastream/ByteBufferDataStream.java b/io/src/main/java/software/amazon/smithy/java/io/datastream/ByteBufferDataStream.java index 7d36d001c..ddb621311 100644 --- a/io/src/main/java/software/amazon/smithy/java/io/datastream/ByteBufferDataStream.java +++ b/io/src/main/java/software/amazon/smithy/java/io/datastream/ByteBufferDataStream.java @@ -5,7 +5,9 @@ package software.amazon.smithy.java.io.datastream; +import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.net.http.HttpRequest; import java.nio.ByteBuffer; import java.util.concurrent.Flow; @@ -48,6 +50,11 @@ public InputStream asInputStream() { return ByteBufferUtils.byteBufferInputStream(buffer.duplicate()); } + @Override + public void writeTo(OutputStream out) throws IOException { + out.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); + } + @Override public long contentLength() { return contentLength; diff --git a/io/src/main/java/software/amazon/smithy/java/io/datastream/DataStream.java b/io/src/main/java/software/amazon/smithy/java/io/datastream/DataStream.java index 5e180d43c..e824c6d9b 100644 --- a/io/src/main/java/software/amazon/smithy/java/io/datastream/DataStream.java +++ b/io/src/main/java/software/amazon/smithy/java/io/datastream/DataStream.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.UncheckedIOException; import java.net.http.HttpRequest; import java.nio.ByteBuffer; @@ -84,6 +85,22 @@ default boolean hasKnownLength() { */ InputStream asInputStream(); + /** + * Write the contents of this stream to the given output stream. + * + *

        This is the preferred way to transfer data from a DataStream to an OutputStream. + * Implementations may override this to avoid intermediate InputStream allocation + * (e.g., writing directly from a byte array or ByteBuffer). + * + * @param out the output stream to write to + * @throws IOException if an I/O error occurs + */ + default void writeTo(OutputStream out) throws IOException { + try (var is = asInputStream()) { + is.transferTo(out); + } + } + /** * Read the contents of the stream into a ByteBuffer by reading all bytes from {@link #asInputStream()}. * diff --git a/io/src/main/java/software/amazon/smithy/java/io/datastream/EmptyDataStream.java b/io/src/main/java/software/amazon/smithy/java/io/datastream/EmptyDataStream.java index 47766564e..1e1b53fcc 100644 --- a/io/src/main/java/software/amazon/smithy/java/io/datastream/EmptyDataStream.java +++ b/io/src/main/java/software/amazon/smithy/java/io/datastream/EmptyDataStream.java @@ -6,6 +6,7 @@ package software.amazon.smithy.java.io.datastream; import java.io.InputStream; +import java.io.OutputStream; import java.net.http.HttpRequest; import java.nio.ByteBuffer; import java.util.concurrent.Flow; @@ -26,6 +27,11 @@ public InputStream asInputStream() { return InputStream.nullInputStream(); } + @Override + public void writeTo(OutputStream out) { + // No-op + } + @Override public boolean isReplayable() { return true; diff --git a/io/src/main/java/software/amazon/smithy/java/io/datastream/InputStreamDataStream.java b/io/src/main/java/software/amazon/smithy/java/io/datastream/InputStreamDataStream.java index d6619912a..971485b8b 100644 --- a/io/src/main/java/software/amazon/smithy/java/io/datastream/InputStreamDataStream.java +++ b/io/src/main/java/software/amazon/smithy/java/io/datastream/InputStreamDataStream.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.UncheckedIOException; final class InputStreamDataStream implements DataStream { @@ -32,6 +33,11 @@ public InputStream asInputStream() { return inputStream; } + @Override + public void writeTo(OutputStream out) throws IOException { + asInputStream().transferTo(out); + } + @Override public boolean isReplayable() { return false; diff --git a/io/src/main/java/software/amazon/smithy/java/io/datastream/PublisherDataStream.java b/io/src/main/java/software/amazon/smithy/java/io/datastream/PublisherDataStream.java index 0dd28a821..50264adcf 100644 --- a/io/src/main/java/software/amazon/smithy/java/io/datastream/PublisherDataStream.java +++ b/io/src/main/java/software/amazon/smithy/java/io/datastream/PublisherDataStream.java @@ -5,10 +5,14 @@ package software.amazon.smithy.java.io.datastream; +import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.net.http.HttpResponse; import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Flow; +import java.util.concurrent.atomic.AtomicReference; final class PublisherDataStream implements DataStream { @@ -71,6 +75,74 @@ public InputStream asInputStream() { return subscriber.getBody().toCompletableFuture().join(); } + @Override + public void writeTo(OutputStream out) throws IOException { + if (!isReplayable && consumed) { + throw new IllegalStateException("DataStream is not replayable and has already been consumed"); + } + consumed = true; + + var error = new AtomicReference(); + var done = new CountDownLatch(1); + + publisher.subscribe(new Flow.Subscriber<>() { + Flow.Subscription subscription; + boolean cancelled; + + @Override + public void onSubscribe(Flow.Subscription s) { + this.subscription = s; + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(ByteBuffer buf) { + if (cancelled) { + return; + } + try { + if (buf.hasArray()) { + out.write(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); + } else { + byte[] tmp = new byte[buf.remaining()]; + buf.get(tmp); + out.write(tmp); + } + } catch (IOException e) { + cancelled = true; + error.set(e); + subscription.cancel(); + done.countDown(); + } + } + + @Override + public void onError(Throwable t) { + error.set(t); + done.countDown(); + } + + @Override + public void onComplete() { + done.countDown(); + } + }); + + try { + done.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Interrupted while writing publisher data", e); + } + + Throwable t = error.get(); + if (t instanceof IOException ioe) { + throw ioe; + } else if (t != null) { + throw new IOException("Publisher error", t); + } + } + private void innerSubscribe(Flow.Subscriber subscriber) { consumed = true; publisher.subscribe(subscriber); diff --git a/io/src/main/java/software/amazon/smithy/java/io/datastream/WrappedDataStream.java b/io/src/main/java/software/amazon/smithy/java/io/datastream/WrappedDataStream.java index b2646d4b6..73ad10209 100644 --- a/io/src/main/java/software/amazon/smithy/java/io/datastream/WrappedDataStream.java +++ b/io/src/main/java/software/amazon/smithy/java/io/datastream/WrappedDataStream.java @@ -5,7 +5,9 @@ package software.amazon.smithy.java.io.datastream; +import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.concurrent.Flow; @@ -33,6 +35,11 @@ public InputStream asInputStream() { return delegate.asInputStream(); } + @Override + public void writeTo(OutputStream out) throws IOException { + delegate.writeTo(out); + } + @Override public long contentLength() { return contentLength; diff --git a/io/src/test/java/software/amazon/smithy/java/io/datastream/ByteBufferDataStreamTest.java b/io/src/test/java/software/amazon/smithy/java/io/datastream/ByteBufferDataStreamTest.java index 9341c97e6..1f9a07732 100644 --- a/io/src/test/java/software/amazon/smithy/java/io/datastream/ByteBufferDataStreamTest.java +++ b/io/src/test/java/software/amazon/smithy/java/io/datastream/ByteBufferDataStreamTest.java @@ -8,7 +8,10 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; @@ -31,4 +34,29 @@ public void isAlwaysAvailable() { ds.asByteBuffer(); assertThat(ds.isAvailable(), is(true)); } + + @Test + public void writeTo() throws IOException { + var data = "hello world".getBytes(StandardCharsets.UTF_8); + var ds = DataStream.ofBytes(data); + var out = new ByteArrayOutputStream(); + + ds.writeTo(out); + + assertArrayEquals(data, out.toByteArray()); + } + + @Test + public void writeToIsReplayable() throws IOException { + var data = "replay".getBytes(StandardCharsets.UTF_8); + var ds = DataStream.ofBytes(data); + + var out1 = new ByteArrayOutputStream(); + ds.writeTo(out1); + var out2 = new ByteArrayOutputStream(); + ds.writeTo(out2); + + assertArrayEquals(data, out1.toByteArray()); + assertArrayEquals(data, out2.toByteArray()); + } } diff --git a/io/src/test/java/software/amazon/smithy/java/io/datastream/InputStreamDataStreamTest.java b/io/src/test/java/software/amazon/smithy/java/io/datastream/InputStreamDataStreamTest.java index d509715d4..e37823e0f 100644 --- a/io/src/test/java/software/amazon/smithy/java/io/datastream/InputStreamDataStreamTest.java +++ b/io/src/test/java/software/amazon/smithy/java/io/datastream/InputStreamDataStreamTest.java @@ -9,7 +9,12 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -87,4 +92,23 @@ public void isNotAvailableAfterConsumption() throws Exception { ds.asInputStream(); assertThat(ds.isAvailable(), is(false)); } + + @Test + public void writeTo() throws IOException { + var data = "from input stream".getBytes(StandardCharsets.UTF_8); + var ds = DataStream.ofInputStream(new ByteArrayInputStream(data)); + var out = new ByteArrayOutputStream(); + + ds.writeTo(out); + + assertArrayEquals(data, out.toByteArray()); + } + + @Test + public void writeToNotReplayable() throws IOException { + var ds = DataStream.ofInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3})); + ds.writeTo(new ByteArrayOutputStream()); + + assertThrows(IllegalStateException.class, () -> ds.writeTo(new ByteArrayOutputStream())); + } } diff --git a/io/src/test/java/software/amazon/smithy/java/io/datastream/PublisherDataStreamTest.java b/io/src/test/java/software/amazon/smithy/java/io/datastream/PublisherDataStreamTest.java new file mode 100644 index 000000000..06566df54 --- /dev/null +++ b/io/src/test/java/software/amazon/smithy/java/io/datastream/PublisherDataStreamTest.java @@ -0,0 +1,63 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.io.datastream; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.SubmissionPublisher; +import org.junit.jupiter.api.Test; + +class PublisherDataStreamTest { + + @Test + void writeTo() throws IOException { + var chunk1 = "hello ".getBytes(StandardCharsets.UTF_8); + var chunk2 = "world".getBytes(StandardCharsets.UTF_8); + + var publisher = new SubmissionPublisher(); + var ds = DataStream.ofPublisher(publisher, null, -1); + var out = new ByteArrayOutputStream(); + + Thread.startVirtualThread(() -> { + publisher.submit(ByteBuffer.wrap(chunk1)); + publisher.submit(ByteBuffer.wrap(chunk2)); + publisher.close(); + }); + + ds.writeTo(out); + + assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8), out.toByteArray()); + } + + @Test + void writeToNotReplayable() throws IOException { + var publisher = new SubmissionPublisher(); + var ds = DataStream.ofPublisher(publisher, null, -1); + + Thread.startVirtualThread(publisher::close); + ds.writeTo(new ByteArrayOutputStream()); + + assertThrows(IllegalStateException.class, () -> ds.writeTo(new ByteArrayOutputStream())); + } + + @Test + void writeToEmpty() throws IOException { + var publisher = new SubmissionPublisher(); + var ds = DataStream.ofPublisher(publisher, null, 0); + var out = new ByteArrayOutputStream(); + + Thread.startVirtualThread(publisher::close); + ds.writeTo(out); + + assertEquals(0, out.size()); + } +} diff --git a/io/src/test/java/software/amazon/smithy/java/io/datastream/WrappedDataStreamTest.java b/io/src/test/java/software/amazon/smithy/java/io/datastream/WrappedDataStreamTest.java index c99e027e1..1d7044365 100644 --- a/io/src/test/java/software/amazon/smithy/java/io/datastream/WrappedDataStreamTest.java +++ b/io/src/test/java/software/amazon/smithy/java/io/datastream/WrappedDataStreamTest.java @@ -8,8 +8,11 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; @@ -33,4 +36,16 @@ public void delegatesIsAvailableToUnderlyingStream() { ds.asInputStream(); assertThat(wrapped.isAvailable(), is(false)); } + + @Test + public void writeToDelegates() throws IOException { + var data = "wrapped".getBytes(StandardCharsets.UTF_8); + var inner = DataStream.ofBytes(data); + var ds = DataStream.withMetadata(inner, "text/plain", (long) data.length, true); + var out = new ByteArrayOutputStream(); + + ds.writeTo(out); + + assertArrayEquals(data, out.toByteArray()); + } } From 28a29fda5b555ae1406587761cff945456832969 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Wed, 18 Feb 2026 11:04:36 -0600 Subject: [PATCH 59/60] Add Smithy client transport --- .../java/client/core/ClientTransport.java | 3 +- client/client-http-smithy/build.gradle.kts | 14 ++ .../smithy/SmithyHttpClientTransport.java | 176 ++++++++++++++++++ .../smithy/SmithyHttpTransportConfig.java | 105 +++++++++++ ...hy.java.client.core.ClientTransportFactory | 1 + .../smithy/SmithyHttpClientTransportTest.java | 82 ++++++++ .../java/client/http/HttpTransportConfig.java | 120 ++++++++++++ .../client/http/JavaHttpClientTransport.java | 21 ++- settings.gradle.kts | 1 + 9 files changed, 514 insertions(+), 9 deletions(-) create mode 100644 client/client-http-smithy/build.gradle.kts create mode 100644 client/client-http-smithy/src/main/java/software/amazon/smithy/java/client/http/smithy/SmithyHttpClientTransport.java create mode 100644 client/client-http-smithy/src/main/java/software/amazon/smithy/java/client/http/smithy/SmithyHttpTransportConfig.java create mode 100644 client/client-http-smithy/src/main/resources/META-INF/services/software.amazon.smithy.java.client.core.ClientTransportFactory create mode 100644 client/client-http-smithy/src/test/java/software/amazon/smithy/java/client/http/smithy/SmithyHttpClientTransportTest.java create mode 100644 client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpTransportConfig.java diff --git a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientTransport.java b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientTransport.java index 7cf520f36..575292065 100644 --- a/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientTransport.java +++ b/client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientTransport.java @@ -5,6 +5,7 @@ package software.amazon.smithy.java.client.core; +import java.io.Closeable; import java.net.ConnectException; import java.net.ProtocolException; import java.net.SocketException; @@ -25,7 +26,7 @@ * @implNote To be discoverable by dynamic clients and client code generators, * ClientTransport's should implement a {@link ClientTransportFactory} service provider. */ -public interface ClientTransport { +public interface ClientTransport extends Closeable { /** * Send a prepared request. * diff --git a/client/client-http-smithy/build.gradle.kts b/client/client-http-smithy/build.gradle.kts new file mode 100644 index 000000000..d5f3c9a75 --- /dev/null +++ b/client/client-http-smithy/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id("smithy-java.module-conventions") +} + +description = "Client transport using Smithy's native HTTP client with full HTTP/2 bidirectional streaming" + +extra["displayName"] = "Smithy :: Java :: Client :: HTTP :: Smithy" +extra["moduleName"] = "software.amazon.smithy.java.client.http.smithy" + +dependencies { + api(project(":client:client-http")) + api(project(":http:http-client")) + implementation(project(":logging")) +} diff --git a/client/client-http-smithy/src/main/java/software/amazon/smithy/java/client/http/smithy/SmithyHttpClientTransport.java b/client/client-http-smithy/src/main/java/software/amazon/smithy/java/client/http/smithy/SmithyHttpClientTransport.java new file mode 100644 index 000000000..fb46aac95 --- /dev/null +++ b/client/client-http-smithy/src/main/java/software/amazon/smithy/java/client/http/smithy/SmithyHttpClientTransport.java @@ -0,0 +1,176 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.client.http.smithy; + +import java.io.IOException; +import java.io.OutputStream; +import software.amazon.smithy.java.client.core.ClientTransport; +import software.amazon.smithy.java.client.core.ClientTransportFactory; +import software.amazon.smithy.java.client.core.MessageExchange; +import software.amazon.smithy.java.client.http.HttpContext; +import software.amazon.smithy.java.client.http.HttpMessageExchange; +import software.amazon.smithy.java.context.Context; +import software.amazon.smithy.java.core.serde.document.Document; +import software.amazon.smithy.java.http.api.HttpHeaders; +import software.amazon.smithy.java.http.api.HttpRequest; +import software.amazon.smithy.java.http.api.HttpResponse; +import software.amazon.smithy.java.http.client.HttpClient; +import software.amazon.smithy.java.http.client.HttpExchange; +import software.amazon.smithy.java.http.client.RequestOptions; +import software.amazon.smithy.java.http.client.connection.HttpConnectionPool; +import software.amazon.smithy.java.io.datastream.DataStream; +import software.amazon.smithy.java.logging.InternalLogger; + +/** + * A client transport using Smithy's native blocking HTTP client with full HTTP/2 bidirectional streaming. + * + *

        Unlike the JDK-based transport, this transport supports true bidirectional streaming over HTTP/2: + * the request body can be written concurrently with reading the response body. For HTTP/1.1, the request + * body is fully sent before the response is returned. + */ +public final class SmithyHttpClientTransport implements ClientTransport { + + private static final InternalLogger LOGGER = InternalLogger.getLogger(SmithyHttpClientTransport.class); + + private final HttpClient client; + + /** + * Create a transport with default settings. + */ + public SmithyHttpClientTransport() { + this(HttpClient.builder().build()); + } + + /** + * Create a transport with the given HTTP client. + * + * @param client the Smithy HTTP client to use + */ + public SmithyHttpClientTransport(HttpClient client) { + this.client = client; + } + + @Override + public MessageExchange messageExchange() { + return HttpMessageExchange.INSTANCE; + } + + @Override + public HttpResponse send(Context context, HttpRequest request) { + try { + return doSend(context, request); + } catch (Exception e) { + throw ClientTransport.remapExceptions(e); + } + } + + private HttpResponse doSend(Context context, HttpRequest request) throws Exception { + var options = RequestOptions.builder() + .requestTimeout(context.get(HttpContext.HTTP_REQUEST_TIMEOUT)) + .build(); + HttpExchange exchange = client.newExchange(request, options); + + try { + DataStream requestBody = request.body(); + boolean hasBody = requestBody != null && requestBody.contentLength() != 0; + if (!hasBody) { + // Close body right away. + exchange.requestBody().close(); + } else if (exchange.supportsBidirectionalStreaming()) { + // H2: write body on a virtual thread so response can stream back concurrently (bidi streaming) + Thread.startVirtualThread(() -> { + try (OutputStream out = exchange.requestBody()) { + requestBody.writeTo(out); + } catch (IOException e) { + LOGGER.debug("Error writing request body: {}", e.getMessage()); + } + }); + } else { + // H1: write body inline. It must complete before response is available. + try (OutputStream out = exchange.requestBody()) { + requestBody.writeTo(out); + } + } + + return buildResponse(exchange); + } catch (Exception e) { + exchange.close(); + throw e; + } + } + + private HttpResponse buildResponse(HttpExchange exchange) throws IOException { + int statusCode = exchange.responseStatusCode(); + HttpHeaders headers = exchange.responseHeaders(); + + var length = headers.contentLength(); + long adaptedLength = length == null ? -1 : length; + var contentType = headers.contentType(); + + // Wrap the response body stream as a DataStream. + // The exchange auto-closes when both request and response streams are closed. + var body = DataStream.ofInputStream(exchange.responseBody(), contentType, adaptedLength); + + return HttpResponse.builder() + .httpVersion(exchange.request().httpVersion()) + .statusCode(statusCode) + .headers(headers) + .body(body) + .build(); + } + + @Override + public void close() throws IOException { + client.close(); + } + + public static final class Factory implements ClientTransportFactory { + @Override + public String name() { + return "http-smithy"; + } + + @Override + public SmithyHttpClientTransport createTransport(Document node) { + var config = new SmithyHttpTransportConfig().fromDocument(node); + + var builder = HttpClient.builder(); + var poolBuilder = HttpConnectionPool.builder(); + + if (config.requestTimeout() != null) { + builder.requestTimeout(config.requestTimeout()); + } + if (config.maxConnections() != null) { + poolBuilder.maxTotalConnections(config.maxConnections()); + poolBuilder.maxConnectionsPerRoute(config.maxConnections()); + } + if (config.h2StreamsPerConnection() != null) { + poolBuilder.h2StreamsPerConnection(config.h2StreamsPerConnection()); + } + if (config.h2InitialWindowSize() != null) { + poolBuilder.h2InitialWindowSize(config.h2InitialWindowSize()); + } + if (config.connectTimeout() != null) { + poolBuilder.connectTimeout(config.connectTimeout()); + } + if (config.maxIdleTime() != null) { + poolBuilder.maxIdleTime(config.maxIdleTime()); + } + if (config.httpVersionPolicy() != null) { + poolBuilder.httpVersionPolicy(config.httpVersionPolicy()); + } + + builder.connectionPool(poolBuilder.build()); + + return new SmithyHttpClientTransport(builder.build()); + } + + @Override + public MessageExchange messageExchange() { + return HttpMessageExchange.INSTANCE; + } + } +} diff --git a/client/client-http-smithy/src/main/java/software/amazon/smithy/java/client/http/smithy/SmithyHttpTransportConfig.java b/client/client-http-smithy/src/main/java/software/amazon/smithy/java/client/http/smithy/SmithyHttpTransportConfig.java new file mode 100644 index 000000000..0c9068e48 --- /dev/null +++ b/client/client-http-smithy/src/main/java/software/amazon/smithy/java/client/http/smithy/SmithyHttpTransportConfig.java @@ -0,0 +1,105 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.client.http.smithy; + +import java.time.Duration; +import software.amazon.smithy.java.client.http.HttpTransportConfig; +import software.amazon.smithy.java.core.serde.document.Document; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; + +/** + * Transport configuration for the Smithy HTTP client, extending common settings + * with connection pool and HTTP/2 tuning options. + */ +public final class SmithyHttpTransportConfig extends HttpTransportConfig { + + private Integer maxConnections; + private Duration maxIdleTime; + private Integer h2StreamsPerConnection; + private Integer h2InitialWindowSize; + private HttpVersionPolicy httpVersionPolicy; + + public Integer maxConnections() { + return maxConnections; + } + + public SmithyHttpTransportConfig maxConnections(int maxConnections) { + this.maxConnections = maxConnections; + return this; + } + + public Duration maxIdleTime() { + return maxIdleTime; + } + + public SmithyHttpTransportConfig maxIdleTime(Duration maxIdleTime) { + this.maxIdleTime = maxIdleTime; + return this; + } + + public Integer h2StreamsPerConnection() { + return h2StreamsPerConnection; + } + + public SmithyHttpTransportConfig h2StreamsPerConnection(int h2StreamsPerConnection) { + this.h2StreamsPerConnection = h2StreamsPerConnection; + return this; + } + + public Integer h2InitialWindowSize() { + return h2InitialWindowSize; + } + + public SmithyHttpTransportConfig h2InitialWindowSize(int h2InitialWindowSize) { + this.h2InitialWindowSize = h2InitialWindowSize; + return this; + } + + public HttpVersionPolicy httpVersionPolicy() { + return httpVersionPolicy; + } + + public SmithyHttpTransportConfig httpVersionPolicy(HttpVersionPolicy httpVersionPolicy) { + this.httpVersionPolicy = httpVersionPolicy; + return this; + } + + @Override + public SmithyHttpTransportConfig fromDocument(Document doc) { + super.fromDocument(doc); + var config = httpConfig(doc); + if (config == null) { + return this; + } + + var maxConns = config.get("maxConnections"); + if (maxConns != null) { + this.maxConnections = maxConns.asInteger(); + } + + var idle = config.get("maxIdleTimeMs"); + if (idle != null) { + this.maxIdleTime = Duration.ofMillis(idle.asLong()); + } + + var h2Streams = config.get("h2StreamsPerConnection"); + if (h2Streams != null) { + this.h2StreamsPerConnection = h2Streams.asInteger(); + } + + var h2Window = config.get("h2InitialWindowSize"); + if (h2Window != null) { + this.h2InitialWindowSize = h2Window.asInteger(); + } + + var versionPolicy = config.get("httpVersionPolicy"); + if (versionPolicy != null) { + this.httpVersionPolicy = HttpVersionPolicy.valueOf(versionPolicy.asString()); + } + + return this; + } +} diff --git a/client/client-http-smithy/src/main/resources/META-INF/services/software.amazon.smithy.java.client.core.ClientTransportFactory b/client/client-http-smithy/src/main/resources/META-INF/services/software.amazon.smithy.java.client.core.ClientTransportFactory new file mode 100644 index 000000000..eaf61a53a --- /dev/null +++ b/client/client-http-smithy/src/main/resources/META-INF/services/software.amazon.smithy.java.client.core.ClientTransportFactory @@ -0,0 +1 @@ +software.amazon.smithy.java.client.http.smithy.SmithyHttpClientTransport$Factory diff --git a/client/client-http-smithy/src/test/java/software/amazon/smithy/java/client/http/smithy/SmithyHttpClientTransportTest.java b/client/client-http-smithy/src/test/java/software/amazon/smithy/java/client/http/smithy/SmithyHttpClientTransportTest.java new file mode 100644 index 000000000..96461ad60 --- /dev/null +++ b/client/client-http-smithy/src/test/java/software/amazon/smithy/java/client/http/smithy/SmithyHttpClientTransportTest.java @@ -0,0 +1,82 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.client.http.smithy; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.Map; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.java.client.http.HttpMessageExchange; +import software.amazon.smithy.java.core.serde.document.Document; +import software.amazon.smithy.java.http.client.connection.HttpVersionPolicy; + +class SmithyHttpClientTransportTest { + + @Test + void defaultConstructorCreatesTransport() { + var transport = new SmithyHttpClientTransport(); + + assertEquals(HttpMessageExchange.INSTANCE, transport.messageExchange()); + } + + @Test + void factorySettings() { + var factory = new SmithyHttpClientTransport.Factory(); + + assertEquals("http-smithy", factory.name()); + assertEquals(HttpMessageExchange.INSTANCE, factory.messageExchange()); + } + + @Test + void configParsesAllFields() { + var config = new SmithyHttpTransportConfig().fromDocument(Document.of(Map.of( + "http", + Document.of(Map.of( + "requestTimeoutMs", + Document.of(5000), + "connectTimeoutMs", + Document.of(3000), + "maxConnections", + Document.of(50), + "maxIdleTimeMs", + Document.of(60000), + "h2StreamsPerConnection", + Document.of(200), + "h2InitialWindowSize", + Document.of(1048576), + "httpVersionPolicy", + Document.of("H2C_PRIOR_KNOWLEDGE")))))); + + assertEquals(5000, config.requestTimeout().toMillis()); + assertEquals(3000, config.connectTimeout().toMillis()); + assertEquals(50, config.maxConnections()); + assertEquals(60000, config.maxIdleTime().toMillis()); + assertEquals(200, config.h2StreamsPerConnection()); + assertEquals(1048576, config.h2InitialWindowSize()); + assertEquals(HttpVersionPolicy.H2C_PRIOR_KNOWLEDGE, config.httpVersionPolicy()); + } + + @Test + void configHandlesMissingHttpKey() { + var config = new SmithyHttpTransportConfig().fromDocument(Document.of(Map.of())); + + assertNull(config.requestTimeout()); + assertNull(config.maxConnections()); + } + + @Test + void factoryCreatesTransportWithVersionPolicy() { + var factory = new SmithyHttpClientTransport.Factory(); + + assertDoesNotThrow(() -> { + factory.createTransport(Document.of(Map.of( + "http", + Document.of(Map.of("httpVersionPolicy", Document.of("H2C_PRIOR_KNOWLEDGE")))))); + }); + } +} diff --git a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpTransportConfig.java b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpTransportConfig.java new file mode 100644 index 000000000..fc45709aa --- /dev/null +++ b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpTransportConfig.java @@ -0,0 +1,120 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.client.http; + +import java.time.Duration; +import java.util.Map; +import software.amazon.smithy.java.core.serde.document.Document; +import software.amazon.smithy.java.http.api.HttpVersion; + +/** + * Common HTTP transport configuration shared across transport implementations. + * + *

        Subclass this to add transport-specific settings. + */ +public class HttpTransportConfig { + + private Duration requestTimeout; + private Duration connectTimeout; + private HttpVersion httpVersion; + + /** + * Per-request timeout. Null means no timeout (use client default). + * + *

        Defined in the document data as milliseconds. + * + * @return request timeout. + */ + public Duration requestTimeout() { + return requestTimeout; + } + + public HttpTransportConfig requestTimeout(Duration requestTimeout) { + this.requestTimeout = requestTimeout; + return this; + } + + /** + * TCP connect timeout. Null means use transport default. + */ + public Duration connectTimeout() { + return connectTimeout; + } + + public HttpTransportConfig connectTimeout(Duration connectTimeout) { + this.connectTimeout = connectTimeout; + return this; + } + + /** + * HTTP version preference. Null means use transport default. + * + *

        Defined in the document data as "http/1.0", "http/1.1", or "h2". + */ + public HttpVersion httpVersion() { + return httpVersion; + } + + public HttpTransportConfig httpVersion(HttpVersion httpVersion) { + this.httpVersion = httpVersion; + return this; + } + + /** + * Populate common fields from a Document config. + * + *

        Expects settings under an {@code "http"} key: + *

        {@code
        +     * {
        +     *   "http": {
        +     *     "requestTimeoutMs": 5000,
        +     *     "connectTimeoutMs": 3000,
        +     *     "httpVersion": "HTTP/2.0"
        +     *   }
        +     * }
        +     * }
        + * + *

        Subclasses should call {@code super.fromDocument(doc)} then parse their own fields + * from {@link #httpConfig(Document)}. + * + * @param doc configuration document + * @return this config for chaining + */ + public HttpTransportConfig fromDocument(Document doc) { + var config = httpConfig(doc); + if (config == null) { + return this; + } + + var timeout = config.get("requestTimeoutMs"); + if (timeout != null) { + this.requestTimeout = Duration.ofMillis(timeout.asLong()); + } + + var connTimeout = config.get("connectTimeoutMs"); + if (connTimeout != null) { + this.connectTimeout = Duration.ofMillis(connTimeout.asLong()); + } + + var version = config.get("httpVersion"); + if (version != null) { + this.httpVersion = HttpVersion.from(version.asString()); + } + + return this; + } + + /** + * Extract the {@code "http"} config map from a Document, or null if absent. + * + * @param doc the root configuration document + * @return the http config map, or null + */ + protected static Map httpConfig(Document doc) { + var http = doc.asStringMap().get("http"); + return http != null ? http.asStringMap() : null; + } +} diff --git a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/JavaHttpClientTransport.java b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/JavaHttpClientTransport.java index 391c427c2..77aa63bcc 100644 --- a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/JavaHttpClientTransport.java +++ b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/JavaHttpClientTransport.java @@ -207,6 +207,11 @@ private static HttpVersion javaToSmithyVersion(HttpClient.Version version) { }; } + @Override + public void close() { + client.close(); + } + public static final class Factory implements ClientTransportFactory { @Override public String name() { @@ -216,15 +221,15 @@ public String name() { @Override public JavaHttpClientTransport createTransport(Document node) { setHostProperties(); - var versionNode = node.asStringMap().get("version"); - HttpClient httpClient; - if (versionNode != null) { - var version = HttpVersion.from(versionNode.asString()); - httpClient = HttpClient.newBuilder().version(smithyToHttpVersion(version)).build(); - } else { - httpClient = HttpClient.newHttpClient(); + var config = new HttpTransportConfig().fromDocument(node); + var builder = HttpClient.newBuilder(); + if (config.httpVersion() != null) { + builder.version(smithyToHttpVersion(config.httpVersion())); + } + if (config.connectTimeout() != null) { + builder.connectTimeout(config.connectTimeout()); } - return new JavaHttpClientTransport(httpClient); + return new JavaHttpClientTransport(builder.build()); } @Override diff --git a/settings.gradle.kts b/settings.gradle.kts index b0d0d0927..970cab4ef 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -43,6 +43,7 @@ include(":client:client-core") include(":client:client-auth-api") include(":client:client-http") include(":client:client-http-binding") +include(":client:client-http-smithy") include(":client:client-rpcv2-cbor") include(":client:dynamic-client") include(":client:client-mock-plugin") From fc34833dd04a5938ca2fdc7cea10c876794c4f3b Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Wed, 18 Feb 2026 15:26:49 -0600 Subject: [PATCH 60/60] Use DataStream.writeTo --- .../software/amazon/smithy/java/http/client/HttpExchange.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java index 1804dde21..d472fdc98 100644 --- a/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java +++ b/http/http-client/src/main/java/software/amazon/smithy/java/http/client/HttpExchange.java @@ -106,7 +106,7 @@ static HttpExchange newBufferedExchange(HttpRequest request, HttpResponse respon */ default void writeRequestBody() throws IOException { try (OutputStream out = requestBody()) { - request().body().asInputStream().transferTo(out); + request().body().writeTo(out); } } @@ -175,6 +175,7 @@ default void writeRequestBody() throws IOException { * try (InputStream in = exchange.responseBody()) { * in.readAllBytes(); // must fully consume body first * } + * * HttpHeaders trailers = exchange.responseTrailerHeaders(); * if (trailers != null) { * String checksum = trailers.firstValue("checksum").orElse(null);