diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 0b26b8467bc228aff3891c6049cc88a7ee82da1f..75434383023f37f3aadf2093fe601be77710cd06 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -10,6 +10,14 @@ sure your code compiles by running `mvn clean verify`. Checkstyle failures during compilation indicate errors in your style and can be viewed in the `checkstyle-result.xml` file. +Some general advice + +- Don’t change public API lightly, avoid if possible, and include your reasoning in the PR if essential. It causes pain for developers who use OkHttp and sometimes runtime errors. +- Favour a working external library if appropriate. There are many examples of OkHttp libraries that can sit on top or hook in via existing APIs. +- Get working code on a personal branch with tests before you submit a PR. +- OkHttp is a small and light dependency. Don't introduce new dependencies or major new functionality. +- OkHttp targets the intersection of RFC correct *and* widely implemented. Incorrect implementations that are very widely implemented e.g. a bug in Apache, Nginx, Google, Firefox should also be handled. + Before your code can be accepted into the project you must also sign the [Individual Contributor License Agreement (CLA)][1]. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index e65d371715a6a17a565f7a98b4ea24a503368804..0000000000000000000000000000000000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,12 +0,0 @@ -What kind of issue is this? - - - [ ] Question. This issue tracker is not the place for questions. If you want to ask how to do - something, or to understand why something isn't working the way you expect it to, use Stack - Overflow. https://stackoverflow.com/questions/tagged/okhttp - - - [ ] Bug report. If you’ve found a bug, spend the time to write a failing test. Bugs with tests - get fixed. Here’s an example: https://gist.github.com/swankjesse/981fcae102f513eb13ed - - - [ ] Feature Request. Start by telling us what problem you’re trying to solve. Often a solution - already exists! Don’t send pull requests to implement new features without first getting our - support. Sometimes we leave features out on purpose to keep the project small. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000000000000000000000000000000..8b276c7deab072018ad02e6e49d09de692005cd0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,13 @@ +--- +name: Bug report +about: A reproducible problem +title: '' +labels: bug +assignees: '' + +--- + +Good bug reports include a failing test! Writing a test helps you to isolate and describe the problem, and it helps us to fix it fast. Bug reports without a failing test or reproduction steps are likely to be closed. + +Here’s an example test to get you started. +https://gist.github.com/swankjesse/981fcae102f513eb13ed diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000000000000000000000000000000..e320e37f4a8e44e03c53d3c2ec8590f065b3d9fa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,12 @@ +--- +name: Feature request +about: Suggest an idea +title: '' +labels: enhancement +assignees: '' + +--- + +Start by telling us what problem you’re trying to solve. Often a solution already exists! + +Don’t send pull requests to implement new features without first getting our support. Sometimes we leave features out on purpose to keep the project small. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000000000000000000000000000000000000..73f2b5c1af2a3c01ecfc451a7ae23d6610171e80 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,16 @@ +--- +name: Question +about: Use Stack Overflow instead +title: "\U0001F649" +labels: '' +assignees: '' + +--- + +🛑 ð™Žð™ð™Šð™‹ + +This issue tracker is not the place for questions! + +If you want to ask how to do something, or to understand why something isn't working the way you expect it to, use Stack Overflow. https://stackoverflow.com/questions/tagged/okhttp + +We close all questions without reading them. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f480f2c40760ecbd4701863542461af1f1f92cb..ac4c0bd4f13a29a3b8caf22fd989faf278b30fe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,58 @@ Change Log ========== +## Version 3.13.0 + +_2019-02-04_ + + * **This release bumps our minimum requirements to Java 8+ or Android 5+.** Cutting off old + devices is a serious change and we don't do it lightly! [This post][require_android_5] explains + why we're doing this and how to upgrade. + + The OkHttp 3.12.x branch will be our long-term branch for Android 2.3+ (API level 9+) and Java + 7+. These platforms lack support for TLS 1.2 and should not be used. But because upgrading is + difficult we will backport critical fixes to the 3.12.x branch through December 31, 2020. + + * **TLSv1 and TLSv1.1 are no longer enabled by default.** Major web browsers are working towards + removing these versions altogether in early 2020. If your servers aren't ready yet you can + configure OkHttp 3.13 to allow TLSv1 and TLSv1.1 connections: + + ``` + OkHttpClient client = new OkHttpClient.Builder() + .connectionSpecs(Arrays.asList(ConnectionSpec.COMPATIBLE_TLS)) + .build(); + ``` + + * New: You can now access HTTP trailers with `Response.trailers()`. This method may only be called + after the entire HTTP response body has been read. + + * New: Upgrade to Okio 1.17.3. If you're on Kotlin-friendly Okio 2.x this release requires 2.2.2 + or newer. + + ```kotlin + implementation("com.squareup.okio:okio:1.17.3") + ``` + + * Fix: Don't miss cancels when sending HTTP/2 request headers. + * Fix: Don't miss whole operation timeouts when calls redirect. + * Fix: Don't leak connections if web sockets have malformed responses or if `onOpen()` throws. + * Fix: Don't retry when request bodies fail due to `FileNotFoundException`. + * Fix: Don't crash when URLs have IPv4-mapped IPv6 addresses. + * Fix: Don't crash when building `HandshakeCertificates` on Android API 28. + * Fix: Permit multipart file names to contain non-ASCII characters. + * New: API to get MockWebServer's dispatcher. + * New: API to access headers as `java.time.Instant`. + * New: Fail fast if a `SSLSocketFactory` is used as a `SocketFactory`. + * New: Log the TLS handshake in `LoggingEventListener`. + + +## Version 3.12.1 + +_2018-12-23_ + + * Fix: Remove overlapping `package-info.java`. This caused issues with some build tools. + + ## Version 3.12.0 _2018-11-16_ @@ -1599,4 +1651,5 @@ Initial release. [remove_cbc_ecdsa]: https://developers.google.com/web/updates/2016/12/chrome-56-deprecations#remove_cbc-mode_ecdsa_ciphers_in_tls [conscrypt]: https://github.com/google/conscrypt/ [conscrypt_dependency]: https://github.com/google/conscrypt/#download - [https_server_sample]: https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/HttpsServer.java \ No newline at end of file + [https_server_sample]: https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/HttpsServer.java + [require_android_5]: https://medium.com/square-corner-blog/okhttp-3-13-requires-android-5-XXXXXXXXXXXX diff --git a/README.md b/README.md index fcecace9525d238b4a11783f4fac523ae71763bb..5a90766d80a196668cbe49a30a8bc096ead98916 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,42 @@ OkHttp ====== -An HTTP & HTTP/2 client for Android and Java applications. For more information see [the website][1] and [the wiki][2]. +An HTTP & HTTP/2 client for Android and Java applications. For more information see [the +website][website] and [the wiki][wiki]. + + +Requirements +------------ + +OkHttp works on Android 5.0+ (API level 21+) and on Java 8+. + +OkHttp has one library dependency on [Okio][okio], a small library for high-performance I/O. It +works with either Okio 1.x (implemented in Java) or Okio 2.x (upgraded to Kotlin). + +We highly recommend you keep OkHttp up-to-date. As with auto-updating web browsers, staying current +with HTTPS clients is an important defense against potential security problems. [We +track][tls_history] the dynamic TLS ecosystem and adjust OkHttp to improve connectivity and +security. + +OkHttp uses your platform's built-in TLS implementation. On Java platforms OkHttp also supports +[Conscrypt][conscrypt], which integrates BoringSSL with Java. OkHttp will use Conscrypt if it is +the first security provider: + +```java +Security.insertProviderAt(Conscrypt.newProvider(), 1); +``` + +The OkHttp 3.12.x branch supports Android 2.3+ (API level 9+) and Java 7+. These platforms lack +support for TLS 1.2 and should not be used. But because upgrading is difficult we will backport +critical fixes to the [3.12.x branch][okhttp_312x] through December 31, 2020. Download -------- -Download [the latest JAR][3] or configure this dependency: +Download [the latest JAR][okhttp_latest_jar] or configure this dependency: ```kotlin -implementation("com.squareup.okhttp3:okhttp:3.12.0") +implementation("com.squareup.okhttp3:okhttp:3.13.0") ``` Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap]. @@ -24,16 +51,15 @@ MockWebServer coupling with OkHttp is essential for proper testing of HTTP/2 so ### Download -Download [the latest JAR][4] or configure this dependency: +Download [the latest JAR][mockwebserver_latest_jar] or configure this dependency: ```xml -testImplementation("com.squareup.okhttp3:mockwebserver:3.12.0") +testImplementation("com.squareup.okhttp3:mockwebserver:3.13.0") ``` R8 / ProGuard ------------- -If you are using R8 or ProGuard add the options from -[this file](https://github.com/square/okhttp/blob/master/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro). +If you are using R8 or ProGuard add the options from [`okhttp3.pro`][okhttp3_pro]. You might also need rules for Okio which is a dependency of this library. @@ -54,8 +80,13 @@ License limitations under the License. - [1]: https://square.github.io/okhttp - [2]: https://github.com/square/okhttp/wiki - [3]: https://search.maven.org/remote_content?g=com.squareup.okhttp3&a=okhttp&v=LATEST - [4]: https://search.maven.org/remote_content?g=com.squareup.okhttp3&a=mockwebserver&v=LATEST + [conscrypt]: https://github.com/google/conscrypt/ + [mockwebserver_latest_jar]: https://search.maven.org/remote_content?g=com.squareup.okhttp3&a=mockwebserver&v=LATEST + [okhttp_312x]: https://github.com/square/okhttp/tree/okhttp_3.12.x + [okhttp_latest_jar]: https://search.maven.org/remote_content?g=com.squareup.okhttp3&a=okhttp&v=LATEST + [okio]: https://github.com/square/okio/ [snap]: https://oss.sonatype.org/content/repositories/snapshots/ + [tls_history]: https://github.com/square/okhttp/wiki/TLS-Configuration-History + [website]: https://square.github.io/okhttp + [wiki]: https://github.com/square/okhttp/wiki + [okhttp3_pro]: https://github.com/square/okhttp/blob/master/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro diff --git a/benchmarks/README.md b/benchmarks/README.md deleted file mode 100644 index ee9be3ff477e818f9f9fa25e43b5c4265b0e6376..0000000000000000000000000000000000000000 --- a/benchmarks/README.md +++ /dev/null @@ -1,8 +0,0 @@ -OkHttp Benchmarks -======================================= - -This module allows you to test the performance of HTTP clients. - -### Running - 1. If you made modifications to `Benchmark` run `mvn compile`. - 2. Run `mvn exec:exec` to launch a new JVM, which will execute the benchmark. diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml deleted file mode 100644 index 82cdc0df17a10c3173cb17df71b273c1b5e80b25..0000000000000000000000000000000000000000 --- a/benchmarks/pom.xml +++ /dev/null @@ -1,145 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - <modelVersion>4.0.0</modelVersion> - - <parent> - <groupId>com.squareup.okhttp3</groupId> - <artifactId>parent</artifactId> - <version>3.12.1</version> - </parent> - - <artifactId>benchmarks</artifactId> - <name>Benchmarks</name> - - <dependencies> - <dependency> - <groupId>com.google.caliper</groupId> - <artifactId>caliper</artifactId> - <version>1.0-beta-1</version> - </dependency> - <!-- caliper needs to be updated to be compatible with guava 16 --> - <dependency> - <groupId>com.google.guava</groupId> - <artifactId>guava</artifactId> - <version>14.0.1</version> - </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>okhttp</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>okhttp-urlconnection</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>okhttp-tls</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>mockwebserver</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>org.apache.httpcomponents</groupId> - <artifactId>httpclient</artifactId> - </dependency> - <dependency> - <groupId>io.netty</groupId> - <artifactId>netty-transport</artifactId> - <version>4.0.15.Final</version> - </dependency> - <dependency> - <groupId>io.netty</groupId> - <artifactId>netty-handler</artifactId> - <version>4.0.15.Final</version> - </dependency> - <dependency> - <groupId>io.netty</groupId> - <artifactId>netty-codec-http</artifactId> - <version>4.0.15.Final</version> - </dependency> - <!-- Netty needs this if gzip is enabled. --> - <dependency> - <groupId>com.jcraft</groupId> - <artifactId>jzlib</artifactId> - <version>1.1.2</version> - </dependency> - </dependencies> - <build> - <plugins> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>exec-maven-plugin</artifactId> - <version>1.5.0</version> - <executions> - <execution> - <goals> - <goal>java</goal> - </goals> - </execution> - </executions> - <configuration> - <executable>java</executable> - <arguments> - <argument>-Xms512m</argument> - <argument>-Xmx512m</argument> - <commandlineArgs>-Xbootclasspath/p:${bootclasspath}</commandlineArgs> - <argument>-classpath</argument> - <classpath /> - <argument>okhttp3.benchmarks.Benchmark</argument> - </arguments> - </configuration> - </plugin> - </plugins> - </build> - <profiles> - <profile> - <id>alpn-when-jdk7</id> - <activation> - <jdk>1.7</jdk> - </activation> - <dependencies> - <dependency> - <groupId>org.mortbay.jetty.alpn</groupId> - <artifactId>alpn-boot</artifactId> - <version>${alpn.jdk7.version}</version> - <scope>provided</scope> - </dependency> - </dependencies> - </profile> - <profile> - <id>alpn-when-jdk8</id> - <activation> - <jdk>1.8</jdk> - </activation> - <dependencies> - <dependency> - <groupId>org.mortbay.jetty.alpn</groupId> - <artifactId>alpn-boot</artifactId> - <version>${alpn.jdk8.version}</version> - <scope>provided</scope> - </dependency> - </dependencies> - <build> - <plugins> - <plugin> - <!-- Fails on caliper's ASM on OpenJDK 8. --> - <groupId>org.codehaus.mojo</groupId> - <artifactId>animal-sniffer-maven-plugin</artifactId> - <version>1.15</version> - <executions> - <execution> - <phase>none</phase> - </execution> - </executions> - </plugin> - </plugins> - </build> - </profile> - </profiles> -</project> diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/ApacheHttpClient.java b/benchmarks/src/main/java/okhttp3/benchmarks/ApacheHttpClient.java deleted file mode 100644 index e3dbd3084cb4be4e6aab8371277b4fc92630cc46..0000000000000000000000000000000000000000 --- a/benchmarks/src/main/java/okhttp3/benchmarks/ApacheHttpClient.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.benchmarks; - -import java.io.IOException; -import java.io.InputStream; -import java.util.concurrent.TimeUnit; -import java.util.zip.GZIPInputStream; -import okhttp3.HttpUrl; -import okhttp3.tls.HandshakeCertificates; -import org.apache.http.Header; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.conn.ClientConnectionManager; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.ssl.SSLSocketFactory; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.conn.PoolingClientConnectionManager; - -import static okhttp3.tls.internal.TlsUtil.localhost; - -/** Benchmark Apache HTTP client. */ -class ApacheHttpClient extends SynchronousHttpClient { - private static final boolean VERBOSE = false; - - private HttpClient client; - - @Override public void prepare(Benchmark benchmark) { - super.prepare(benchmark); - ClientConnectionManager connectionManager = new PoolingClientConnectionManager(); - if (benchmark.tls) { - HandshakeCertificates handshakeCertificates = localhost(); - connectionManager.getSchemeRegistry().register( - new Scheme("https", 443, new SSLSocketFactory(handshakeCertificates.sslContext()))); - } - client = new DefaultHttpClient(connectionManager); - } - - @Override public Runnable request(HttpUrl url) { - return new ApacheHttpClientRequest(url); - } - - class ApacheHttpClientRequest implements Runnable { - private final HttpUrl url; - - ApacheHttpClientRequest(HttpUrl url) { - this.url = url; - } - - public void run() { - long start = System.nanoTime(); - try { - HttpResponse response = client.execute(new HttpGet(url.toString())); - InputStream in = response.getEntity().getContent(); - Header contentEncoding = response.getFirstHeader("Content-Encoding"); - if (contentEncoding != null && contentEncoding.getValue().equals("gzip")) { - in = new GZIPInputStream(in); - } - - long total = readAllAndClose(in); - long finish = System.nanoTime(); - - if (VERBOSE) { - System.out.println(String.format("Transferred % 8d bytes in %4d ms", - total, TimeUnit.NANOSECONDS.toMillis(finish - start))); - } - } catch (IOException e) { - System.out.println("Failed: " + e); - } - } - } -} diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/Benchmark.java b/benchmarks/src/main/java/okhttp3/benchmarks/Benchmark.java deleted file mode 100644 index 081fcfccb9f09f1885f94b59b863fbd0a027fb40..0000000000000000000000000000000000000000 --- a/benchmarks/src/main/java/okhttp3/benchmarks/Benchmark.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.benchmarks; - -import com.google.caliper.Param; -import com.google.caliper.model.ArbitraryMeasurement; -import com.google.caliper.runner.CaliperMain; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; -import okhttp3.HttpUrl; -import okhttp3.Protocol; -import okhttp3.mockwebserver.Dispatcher; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import okhttp3.tls.HandshakeCertificates; -import okio.Buffer; -import okio.GzipSink; - -import static okhttp3.tls.internal.TlsUtil.localhost; - -/** - * This benchmark is fake, but may be useful for certain relative comparisons. It uses a local - * connection to a MockWebServer to measure how many identical requests per second can be carried - * over a fixed number of threads. - */ -public class Benchmark extends com.google.caliper.Benchmark { - private static final int NUM_REPORTS = 10; - private static final boolean VERBOSE = false; - - private final Random random = new Random(0); - - /** Which client to run. */ - @Param - Client client; - - /** How many concurrent requests to execute. */ - @Param({"1", "10"}) - int concurrencyLevel; - - /** How many requests to enqueue to await threads to execute them. */ - @Param({"10"}) - int targetBacklog; - - /** True to use TLS. */ - // TODO: compare different ciphers? - @Param - boolean tls; - - /** True to use gzip content-encoding for the response body. */ - @Param - boolean gzip; - - /** Don't combine chunked with HTTP_2; that's not allowed. */ - @Param - boolean chunked; - - /** The size of the HTTP response body, in uncompressed bytes. */ - @Param({"128", "1048576"}) - int bodyByteCount; - - /** How many additional headers were included, beyond the built-in ones. */ - @Param({"0", "20"}) - int headerCount; - - /** Which ALPN protocols are in use. Only useful with TLS. */ - List<Protocol> protocols = Arrays.asList(Protocol.HTTP_1_1); - - public static void main(String[] args) { - List<String> allArgs = new ArrayList<>(); - allArgs.add("--instrument"); - allArgs.add("arbitrary"); - allArgs.addAll(Arrays.asList(args)); - - CaliperMain.main(Benchmark.class, allArgs.toArray(new String[allArgs.size()])); - } - - @ArbitraryMeasurement(description = "requests per second") - public double run() throws Exception { - if (VERBOSE) System.out.println(toString()); - HttpClient httpClient = client.create(); - - // Prepare the client & server - httpClient.prepare(this); - MockWebServer server = startServer(); - HttpUrl url = server.url("/"); - - int requestCount = 0; - long reportStart = System.nanoTime(); - long reportPeriod = TimeUnit.SECONDS.toNanos(1); - int reports = 0; - double best = 0.0; - - // Run until we've printed enough reports. - while (reports < NUM_REPORTS) { - // Print a report if we haven't recently. - long now = System.nanoTime(); - double reportDuration = now - reportStart; - if (reportDuration > reportPeriod) { - double requestsPerSecond = requestCount / reportDuration * TimeUnit.SECONDS.toNanos(1); - if (VERBOSE) { - System.out.println(String.format("Requests per second: %.1f", requestsPerSecond)); - } - best = Math.max(best, requestsPerSecond); - requestCount = 0; - reportStart = now; - reports++; - } - - // Fill the job queue with work. - while (httpClient.acceptingJobs()) { - httpClient.enqueue(url); - requestCount++; - } - - // The job queue is full. Take a break. - sleep(1); - } - - return best; - } - - @Override public String toString() { - List<Object> modifiers = new ArrayList<>(); - if (tls) modifiers.add("tls"); - if (gzip) modifiers.add("gzip"); - if (chunked) modifiers.add("chunked"); - modifiers.addAll(protocols); - - return String.format("%s %s\nbodyByteCount=%s headerCount=%s concurrencyLevel=%s", - client, modifiers, bodyByteCount, headerCount, concurrencyLevel); - } - - private void sleep(int millis) { - try { - Thread.sleep(millis); - } catch (InterruptedException ignored) { - } - } - - private MockWebServer startServer() throws IOException { - Logger.getLogger(MockWebServer.class.getName()).setLevel(Level.WARNING); - MockWebServer server = new MockWebServer(); - - if (tls) { - HandshakeCertificates handshakeCertificates = localhost(); - server.useHttps(handshakeCertificates.sslSocketFactory(), false); - server.setProtocols(protocols); - } - - final MockResponse response = newResponse(); - server.setDispatcher(new Dispatcher() { - @Override public MockResponse dispatch(RecordedRequest request) { - return response; - } - }); - - server.start(); - return server; - } - - private MockResponse newResponse() throws IOException { - byte[] bytes = new byte[bodyByteCount]; - random.nextBytes(bytes); - Buffer body = new Buffer().write(bytes); - - MockResponse result = new MockResponse(); - - if (gzip) { - Buffer gzipBody = new Buffer(); - GzipSink gzipSink = new GzipSink(gzipBody); - gzipSink.write(body, body.size()); - gzipSink.close(); - body = gzipBody; - result.addHeader("Content-Encoding: gzip"); - } - - if (chunked) { - result.setChunkedBody(body, 1024); - } else { - result.setBody(body); - } - - for (int i = 0; i < headerCount; i++) { - result.addHeader(randomString(12), randomString(20)); - } - - return result; - } - - private String randomString(int length) { - String alphabet = "-abcdefghijklmnopqrstuvwxyz"; - char[] result = new char[length]; - for (int i = 0; i < length; i++) { - result[i] = alphabet.charAt(random.nextInt(alphabet.length())); - } - return new String(result); - } -} diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/Client.java b/benchmarks/src/main/java/okhttp3/benchmarks/Client.java deleted file mode 100644 index b7c6b3f3b9ba8892980b7e816908d6f7e98f9a7d..0000000000000000000000000000000000000000 --- a/benchmarks/src/main/java/okhttp3/benchmarks/Client.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.benchmarks; - -enum Client { - OkHttp { - @Override HttpClient create() { - return new OkHttp(); - } - }, - - OkHttpAsync { - @Override HttpClient create() { - return new OkHttpAsync(); - } - }, - - Apache { - @Override HttpClient create() { - return new ApacheHttpClient(); - } - }, - - UrlConnection { - @Override HttpClient create() { - return new UrlConnection(); - } - }, - - Netty { - @Override HttpClient create() { - return new NettyHttpClient(); - } - }; - - abstract HttpClient create(); -} diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/NettyHttpClient.java b/benchmarks/src/main/java/okhttp3/benchmarks/NettyHttpClient.java deleted file mode 100644 index 35731fa3b1cc82127455ee4c0c158341109beaac..0000000000000000000000000000000000000000 --- a/benchmarks/src/main/java/okhttp3/benchmarks/NettyHttpClient.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.benchmarks; - -import io.netty.bootstrap.Bootstrap; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.PooledByteBufAllocator; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.ChannelPipeline; -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.http.DefaultFullHttpRequest; -import io.netty.handler.codec.http.HttpClientCodec; -import io.netty.handler.codec.http.HttpContent; -import io.netty.handler.codec.http.HttpContentDecompressor; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.handler.codec.http.HttpVersion; -import io.netty.handler.codec.http.LastHttpContent; -import io.netty.handler.ssl.SslHandler; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.concurrent.TimeUnit; -import javax.net.ssl.SSLEngine; -import okhttp3.HttpUrl; -import okhttp3.tls.HandshakeCertificates; - -import static okhttp3.tls.internal.TlsUtil.localhost; - -/** Netty isn't an HTTP client, but it's almost one. */ -class NettyHttpClient implements HttpClient { - private static final boolean VERBOSE = false; - - // Guarded by this. Real apps need more capable connection management. - private final Deque<HttpChannel> freeChannels = new ArrayDeque<>(); - private final Deque<HttpUrl> backlog = new ArrayDeque<>(); - - private int totalChannels = 0; - private int concurrencyLevel; - private int targetBacklog; - private Bootstrap bootstrap; - - @Override public void prepare(final Benchmark benchmark) { - this.concurrencyLevel = benchmark.concurrencyLevel; - this.targetBacklog = benchmark.targetBacklog; - - ChannelInitializer<SocketChannel> channelInitializer = new ChannelInitializer<SocketChannel>() { - @Override public void initChannel(SocketChannel channel) { - ChannelPipeline pipeline = channel.pipeline(); - - if (benchmark.tls) { - HandshakeCertificates handshakeCertificates = localhost(); - SSLEngine engine = handshakeCertificates.sslContext().createSSLEngine(); - engine.setUseClientMode(true); - pipeline.addLast("ssl", new SslHandler(engine)); - } - - pipeline.addLast("codec", new HttpClientCodec()); - pipeline.addLast("inflater", new HttpContentDecompressor()); - pipeline.addLast("handler", new HttpChannel(channel)); - } - }; - - bootstrap = new Bootstrap(); - bootstrap.group(new NioEventLoopGroup(concurrencyLevel)) - .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) - .channel(NioSocketChannel.class) - .handler(channelInitializer); - } - - @Override public void enqueue(HttpUrl url) throws Exception { - HttpChannel httpChannel = null; - synchronized (this) { - if (!freeChannels.isEmpty()) { - httpChannel = freeChannels.pop(); - } else if (totalChannels < concurrencyLevel) { - totalChannels++; // Create a new channel. (outside of the synchronized block). - } else { - backlog.add(url); // Enqueue this for later, to be picked up when another request completes. - return; - } - } - if (httpChannel == null) { - Channel channel = bootstrap.connect(url.host(), url.port()) - .sync().channel(); - httpChannel = (HttpChannel) channel.pipeline().last(); - } - httpChannel.sendRequest(url); - } - - @Override public synchronized boolean acceptingJobs() { - return backlog.size() < targetBacklog || hasFreeChannels(); - } - - private boolean hasFreeChannels() { - int activeChannels = totalChannels - freeChannels.size(); - return activeChannels < concurrencyLevel; - } - - private void release(HttpChannel httpChannel) { - HttpUrl url; - synchronized (this) { - url = backlog.pop(); - if (url == null) { - // There were no URLs in the backlog. Pool this channel for later. - freeChannels.push(httpChannel); - return; - } - } - - // We removed a URL from the backlog. Schedule it right away. - httpChannel.sendRequest(url); - } - - class HttpChannel extends SimpleChannelInboundHandler<HttpObject> { - private final SocketChannel channel; - byte[] buffer = new byte[1024]; - int total; - long start; - - HttpChannel(SocketChannel channel) { - this.channel = channel; - } - - private void sendRequest(HttpUrl url) { - start = System.nanoTime(); - total = 0; - HttpRequest request = new DefaultFullHttpRequest( - HttpVersion.HTTP_1_1, HttpMethod.GET, url.encodedPath()); - request.headers().set(HttpHeaders.Names.HOST, url.host()); - request.headers().set(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP); - channel.writeAndFlush(request); - } - - @Override protected void channelRead0( - ChannelHandlerContext context, HttpObject message) { - if (message instanceof HttpResponse) { - receive((HttpResponse) message); - } - if (message instanceof HttpContent) { - receive((HttpContent) message); - if (message instanceof LastHttpContent) { - release(this); - } - } - } - - @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { - super.channelInactive(ctx); - } - - void receive(HttpResponse response) { - // Don't do anything with headers. - } - - void receive(HttpContent content) { - // Consume the response body. - ByteBuf byteBuf = content.content(); - for (int toRead; (toRead = byteBuf.readableBytes()) > 0; ) { - byteBuf.readBytes(buffer, 0, Math.min(buffer.length, toRead)); - total += toRead; - } - - if (VERBOSE && content instanceof LastHttpContent) { - long finish = System.nanoTime(); - System.out.println(String.format("Transferred % 8d bytes in %4d ms", - total, TimeUnit.NANOSECONDS.toMillis(finish - start))); - } - } - - @Override public void exceptionCaught(ChannelHandlerContext context, Throwable cause) { - System.out.println("Failed: " + cause); - } - } -} diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/OkHttp.java b/benchmarks/src/main/java/okhttp3/benchmarks/OkHttp.java deleted file mode 100644 index 86b44523a21b63b9eb7a2e916d25ceebe76d6208..0000000000000000000000000000000000000000 --- a/benchmarks/src/main/java/okhttp3/benchmarks/OkHttp.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.benchmarks; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocketFactory; -import okhttp3.Call; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.ResponseBody; -import okhttp3.tls.HandshakeCertificates; - -import static okhttp3.tls.internal.TlsUtil.localhost; - -class OkHttp extends SynchronousHttpClient { - private static final boolean VERBOSE = false; - - private OkHttpClient client; - - @Override public void prepare(Benchmark benchmark) { - super.prepare(benchmark); - client = new OkHttpClient.Builder() - .protocols(benchmark.protocols) - .build(); - - if (benchmark.tls) { - HandshakeCertificates handshakeCertificates = localhost(); - SSLSocketFactory socketFactory = handshakeCertificates.sslSocketFactory(); - HostnameVerifier hostnameVerifier = new HostnameVerifier() { - @Override public boolean verify(String s, SSLSession session) { - return true; - } - }; - client = new OkHttpClient.Builder() - .sslSocketFactory(socketFactory, handshakeCertificates.trustManager()) - .hostnameVerifier(hostnameVerifier) - .build(); - } - } - - @Override public Runnable request(HttpUrl url) { - Call call = client.newCall(new Request.Builder().url(url).build()); - return new OkHttpRequest(call); - } - - class OkHttpRequest implements Runnable { - private final Call call; - - OkHttpRequest(Call call) { - this.call = call; - } - - public void run() { - long start = System.nanoTime(); - try { - ResponseBody body = call.execute().body(); - long total = readAllAndClose(body.byteStream()); - long finish = System.nanoTime(); - - if (VERBOSE) { - System.out.println(String.format("Transferred % 8d bytes in %4d ms", - total, TimeUnit.NANOSECONDS.toMillis(finish - start))); - } - } catch (IOException e) { - System.out.println("Failed: " + e); - } - } - } -} diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/OkHttpAsync.java b/benchmarks/src/main/java/okhttp3/benchmarks/OkHttpAsync.java deleted file mode 100644 index d6d0bc5cb4fc90be9081690815bec2a9fe999139..0000000000000000000000000000000000000000 --- a/benchmarks/src/main/java/okhttp3/benchmarks/OkHttpAsync.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.benchmarks; - -import java.io.IOException; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocketFactory; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Dispatcher; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.tls.HandshakeCertificates; - -import static okhttp3.tls.internal.TlsUtil.localhost; - -class OkHttpAsync implements HttpClient { - private static final boolean VERBOSE = false; - - private final AtomicInteger requestsInFlight = new AtomicInteger(); - - private OkHttpClient client; - private Callback callback; - private int concurrencyLevel; - private int targetBacklog; - - @Override public void prepare(final Benchmark benchmark) { - concurrencyLevel = benchmark.concurrencyLevel; - targetBacklog = benchmark.targetBacklog; - - client = new OkHttpClient.Builder() - .protocols(benchmark.protocols) - .dispatcher(new Dispatcher(new ThreadPoolExecutor(benchmark.concurrencyLevel, - benchmark.concurrencyLevel, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()))) - .build(); - - if (benchmark.tls) { - HandshakeCertificates handshakeCertificates = localhost(); - SSLSocketFactory socketFactory = handshakeCertificates.sslSocketFactory(); - HostnameVerifier hostnameVerifier = new HostnameVerifier() { - @Override public boolean verify(String s, SSLSession session) { - return true; - } - }; - client = client.newBuilder() - .sslSocketFactory(socketFactory, handshakeCertificates.trustManager()) - .hostnameVerifier(hostnameVerifier) - .build(); - } - - callback = new Callback() { - @Override public void onFailure(Call call, IOException e) { - System.out.println("Failed: " + e); - } - - @Override public void onResponse(Call call, Response response) throws IOException { - ResponseBody body = response.body(); - long total = SynchronousHttpClient.readAllAndClose(body.byteStream()); - long finish = System.nanoTime(); - if (VERBOSE) { - long start = (Long) response.request().tag(); - System.out.printf("Transferred % 8d bytes in %4d ms%n", - total, TimeUnit.NANOSECONDS.toMillis(finish - start)); - } - requestsInFlight.decrementAndGet(); - } - }; - } - - @Override public void enqueue(HttpUrl url) throws Exception { - requestsInFlight.incrementAndGet(); - client.newCall(new Request.Builder().tag(System.nanoTime()).url(url).build()).enqueue(callback); - } - - @Override public synchronized boolean acceptingJobs() { - return requestsInFlight.get() < (concurrencyLevel + targetBacklog); - } -} diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/SynchronousHttpClient.java b/benchmarks/src/main/java/okhttp3/benchmarks/SynchronousHttpClient.java deleted file mode 100644 index 4a17b8693de9989f969370ea8f8192d91904c60e..0000000000000000000000000000000000000000 --- a/benchmarks/src/main/java/okhttp3/benchmarks/SynchronousHttpClient.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.benchmarks; - -import java.io.IOException; -import java.io.InputStream; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import okhttp3.HttpUrl; - -/** Any HTTP client with a blocking API. */ -abstract class SynchronousHttpClient implements HttpClient { - ThreadPoolExecutor executor; - int targetBacklog; - - @Override public void prepare(Benchmark benchmark) { - this.targetBacklog = benchmark.targetBacklog; - executor = new ThreadPoolExecutor(benchmark.concurrencyLevel, benchmark.concurrencyLevel, - 1, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); - } - - @Override public void enqueue(HttpUrl url) { - executor.execute(request(url)); - } - - @Override public boolean acceptingJobs() { - return executor.getQueue().size() < targetBacklog; - } - - static long readAllAndClose(InputStream in) throws IOException { - byte[] buffer = new byte[1024]; - long total = 0; - for (int count; (count = in.read(buffer)) != -1; ) { - total += count; - } - in.close(); - return total; - } - - abstract Runnable request(HttpUrl url); -} diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/UrlConnection.java b/benchmarks/src/main/java/okhttp3/benchmarks/UrlConnection.java deleted file mode 100644 index 96eb7422cd75c080e8dc677caaee99c18bfe7593..0000000000000000000000000000000000000000 --- a/benchmarks/src/main/java/okhttp3/benchmarks/UrlConnection.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.benchmarks; - -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.util.concurrent.TimeUnit; -import java.util.zip.GZIPInputStream; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocketFactory; -import okhttp3.HttpUrl; -import okhttp3.tls.HandshakeCertificates; - -import static okhttp3.tls.internal.TlsUtil.localhost; - -class UrlConnection extends SynchronousHttpClient { - private static final boolean VERBOSE = false; - - @Override public void prepare(Benchmark benchmark) { - super.prepare(benchmark); - if (benchmark.tls) { - HandshakeCertificates handshakeCertificates = localhost(); - SSLSocketFactory socketFactory = handshakeCertificates.sslSocketFactory(); - HostnameVerifier hostnameVerifier = new HostnameVerifier() { - @Override public boolean verify(String s, SSLSession session) { - return true; - } - }; - HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); - HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory); - } - } - - @Override public Runnable request(HttpUrl url) { - return new UrlConnectionRequest(url); - } - - static class UrlConnectionRequest implements Runnable { - private final HttpUrl url; - - UrlConnectionRequest(HttpUrl url) { - this.url = url; - } - - public void run() { - long start = System.nanoTime(); - try { - HttpURLConnection urlConnection = (HttpURLConnection) url.url().openConnection(); - InputStream in = urlConnection.getInputStream(); - if ("gzip".equals(urlConnection.getHeaderField("Content-Encoding"))) { - in = new GZIPInputStream(in); - } - - long total = readAllAndClose(in); - long finish = System.nanoTime(); - - if (VERBOSE) { - System.out.println(String.format("Transferred % 8d bytes in %4d ms", - total, TimeUnit.NANOSECONDS.toMillis(finish - start))); - } - } catch (IOException e) { - System.out.println("Failed: " + e); - } - } - } -} diff --git a/bom/pom.xml b/bom/pom.xml deleted file mode 100644 index dac7c59a2472165bb5623d23d8f2680bea816b53..0000000000000000000000000000000000000000 --- a/bom/pom.xml +++ /dev/null @@ -1,81 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - <modelVersion>4.0.0</modelVersion> - - <parent> - <groupId>com.squareup.okhttp3</groupId> - <artifactId>parent</artifactId> - <version>3.12.1</version> - </parent> - - <artifactId>okhttp-bom</artifactId> - <packaging>pom</packaging> - <name>OkHttp (Bill of Materials)</name> - - <dependencyManagement> - <dependencies> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>okhttp</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>okhttp-tests</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>okhttp-android-support</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>okhttp-apache</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>okhttp-sse</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>okhttp-testing-support</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>okhttp-tls</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>okhttp-urlconnection</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>okhttp-logging-interceptor</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>okhttp-dnsoverhttps</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>okcurl</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>mockwebserver</artifactId> - <version>${project.version}</version> - </dependency> - </dependencies> - </dependencyManagement> - -</project> diff --git a/mockwebserver/pom.xml b/mockwebserver/pom.xml index 11f66cf42aa35bd4f27767c8a47a90d8fda806a3..da4c7bd07f45328cf16bad15d938f42b5ce0212d 100644 --- a/mockwebserver/pom.xml +++ b/mockwebserver/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3</groupId> <artifactId>parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>mockwebserver</artifactId> @@ -41,7 +41,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>2.10.4</version> + <version>3.0.1</version> <configuration> <links> <link>http://square.github.io/okhttp/javadoc/</link> @@ -52,7 +52,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> - <version>3.0.0</version> + <version>3.1.0</version> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> @@ -70,7 +70,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.1.1</version> <configuration> <archive> <manifestEntries> diff --git a/mockwebserver/src/main/java/okhttp3/internal/duplex/MwsDuplexAccess.java b/mockwebserver/src/main/java/okhttp3/internal/duplex/MwsDuplexAccess.java new file mode 100644 index 0000000000000000000000000000000000000000..d1eea003978b104b58ba799b1a282f845080dca7 --- /dev/null +++ b/mockwebserver/src/main/java/okhttp3/internal/duplex/MwsDuplexAccess.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package okhttp3.internal.duplex; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.internal.duplex.DuplexResponseBody; + +/** + * Internal access to MockWebServer APIs. Don't use this, don't use internal, these APIs are not + * stable. + */ +public abstract class MwsDuplexAccess { + public static MwsDuplexAccess instance; + + public abstract void setBody(MockResponse mockResponse, DuplexResponseBody duplexResponseBody); +} diff --git a/mockwebserver/src/main/java/okhttp3/mockwebserver/MockResponse.java b/mockwebserver/src/main/java/okhttp3/mockwebserver/MockResponse.java index 36457229ed6fef8d2592af669797f9b3666bd90e..2c9ad33b49fa3972e23442f86fae4ce406e239ce 100644 --- a/mockwebserver/src/main/java/okhttp3/mockwebserver/MockResponse.java +++ b/mockwebserver/src/main/java/okhttp3/mockwebserver/MockResponse.java @@ -22,6 +22,7 @@ import okhttp3.Headers; import okhttp3.WebSocketListener; import okhttp3.internal.Internal; import okhttp3.internal.http2.Settings; +import okhttp3.mockwebserver.internal.duplex.DuplexResponseBody; import okio.Buffer; /** A scripted response to be replayed by the mock web server. */ @@ -30,6 +31,7 @@ public final class MockResponse implements Cloneable { private String status; private Headers.Builder headers = new Headers.Builder(); + private Headers.Builder trailers = new Headers.Builder(); private Buffer body; @@ -49,6 +51,7 @@ public final class MockResponse implements Cloneable { private List<PushPromise> promises = new ArrayList<>(); private Settings settings; private WebSocketListener webSocketListener; + private DuplexResponseBody duplexResponseBody; /** Creates a new mock response with an empty body. */ public MockResponse() { @@ -98,6 +101,10 @@ public final class MockResponse implements Cloneable { return headers.build(); } + public Headers getTrailers() { + return trailers.build(); + } + /** * Removes all HTTP headers including any "Content-Length" and "Transfer-encoding" headers that * were added by default. @@ -143,18 +150,32 @@ public final class MockResponse implements Cloneable { return addHeader(name, value); } - /** Replaces all headers with those specified in {@code headers}. */ + /** Replaces all headers with those specified. */ public MockResponse setHeaders(Headers headers) { this.headers = headers.newBuilder(); return this; } + /** Replaces all trailers with those specified. */ + public MockResponse setTrailers(Headers trailers) { + this.trailers = trailers.newBuilder(); + return this; + } + /** Removes all headers named {@code name}. */ public MockResponse removeHeader(String name) { headers.removeAll(name); return this; } + boolean isDuplex() { + return duplexResponseBody != null; + } + + DuplexResponseBody getDuplexResponseBody() { + return duplexResponseBody; + } + /** Returns a copy of the raw HTTP payload. */ public Buffer getBody() { return body != null ? body.clone() : null; @@ -171,6 +192,11 @@ public final class MockResponse implements Cloneable { return setBody(new Buffer().writeUtf8(body)); } + MockResponse setBody(DuplexResponseBody duplexResponseBody) { + this.duplexResponseBody = duplexResponseBody; + return this; + } + /** * Sets the response body to {@code body}, chunked every {@code maxChunkSize} bytes. */ @@ -186,7 +212,7 @@ public final class MockResponse implements Cloneable { bytesOut.write(body, chunkSize); bytesOut.writeUtf8("\r\n"); } - bytesOut.writeUtf8("0\r\n\r\n"); // Last chunk + empty trailer + CRLF. + bytesOut.writeUtf8("0\r\n"); // Last chunk. Trailers follow! this.body = bytesOut; return this; diff --git a/mockwebserver/src/main/java/okhttp3/mockwebserver/MockWebServer.java b/mockwebserver/src/main/java/okhttp3/mockwebserver/MockWebServer.java index d2a532dd6f7f578510bf874c47783fbd8a7c6d0b..abdbda7148428f027f76c3639453b66a39ca7c70 100644 --- a/mockwebserver/src/main/java/okhttp3/mockwebserver/MockWebServer.java +++ b/mockwebserver/src/main/java/okhttp3/mockwebserver/MockWebServer.java @@ -59,6 +59,7 @@ import okhttp3.Response; import okhttp3.internal.Internal; import okhttp3.internal.NamedRunnable; import okhttp3.internal.Util; +import okhttp3.internal.duplex.MwsDuplexAccess; import okhttp3.internal.http.HttpMethod; import okhttp3.internal.http2.ErrorCode; import okhttp3.internal.http2.Header; @@ -68,6 +69,7 @@ import okhttp3.internal.http2.Settings; import okhttp3.internal.platform.Platform; import okhttp3.internal.ws.RealWebSocket; import okhttp3.internal.ws.WebSocketProtocol; +import okhttp3.mockwebserver.internal.duplex.DuplexResponseBody; import okio.Buffer; import okio.BufferedSink; import okio.BufferedSource; @@ -101,6 +103,12 @@ import static okhttp3.mockwebserver.SocketPolicy.UPGRADE_TO_SSL_AT_END; public final class MockWebServer extends ExternalResource implements Closeable { static { Internal.initializeInstanceForTests(); + MwsDuplexAccess.instance = new MwsDuplexAccess() { + @Override public void setBody( + MockResponse mockResponse, DuplexResponseBody duplexResponseBody) { + mockResponse.setBody(duplexResponseBody); + } + }; } private static final int CLIENT_AUTH_NONE = 0; @@ -127,9 +135,9 @@ public final class MockWebServer extends ExternalResource implements Closeable { private final BlockingQueue<RecordedRequest> requestQueue = new LinkedBlockingQueue<>(); private final Set<Socket> openClientSockets = - Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>()); + Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set<Http2Connection> openConnections = - Collections.newSetFromMap(new ConcurrentHashMap<Http2Connection, Boolean>()); + Collections.newSetFromMap(new ConcurrentHashMap<>()); private final AtomicInteger requestCount = new AtomicInteger(); private long bodyLimit = Long.MAX_VALUE; private ServerSocketFactory serverSocketFactory = ServerSocketFactory.getDefault(); @@ -751,7 +759,19 @@ public final class MockWebServer extends ExternalResource implements Closeable { sink.writeUtf8(response.getStatus()); sink.writeUtf8("\r\n"); - Headers headers = response.getHeaders(); + writeHeaders(sink, response.getHeaders()); + + Buffer body = response.getBody(); + if (body == null) return; + sleepIfDelayed(response.getBodyDelay(TimeUnit.MILLISECONDS)); + throttledTransfer(response, socket, body, sink, body.size(), false); + + if ("chunked".equalsIgnoreCase(response.getHeaders().get("Transfer-Encoding"))) { + writeHeaders(sink, response.getTrailers()); + } + } + + private void writeHeaders(BufferedSink sink, Headers headers) throws IOException { for (int i = 0, size = headers.size(); i < size; i++) { sink.writeUtf8(headers.name(i)); sink.writeUtf8(": "); @@ -760,11 +780,6 @@ public final class MockWebServer extends ExternalResource implements Closeable { } sink.writeUtf8("\r\n"); sink.flush(); - - Buffer body = response.getBody(); - if (body == null) return; - sleepIfDelayed(response.getBodyDelay(TimeUnit.MILLISECONDS)); - throttledTransfer(response, socket, body, sink, body.size(), false); } private void sleepIfDelayed(long delayMs) { @@ -835,6 +850,14 @@ public final class MockWebServer extends ExternalResource implements Closeable { if (line.length() != 0) throw new IllegalStateException("Expected empty but was: " + line); } + /** + * Returns the dispatcher used to respond to HTTP requests. The default dispatcher is a {@link + * QueueDispatcher} but other dispatchers can be configured. + */ + public Dispatcher getDispatcher() { + return dispatcher; + } + /** * Sets the dispatcher used to match incoming requests to mock responses. The default dispatcher * simply serves a fixed sequence of responses from a {@link #enqueue(MockResponse) queue}; custom @@ -924,7 +947,7 @@ public final class MockWebServer extends ExternalResource implements Closeable { socket.close(); return; } - writeResponse(stream, response); + writeResponse(stream, request, response); if (logger.isLoggable(Level.INFO)) { logger.info(MockWebServer.this + " received request: " + request + " and responded: " + response + " protocol is " + protocol.toString()); @@ -962,9 +985,12 @@ public final class MockWebServer extends ExternalResource implements Closeable { Headers headers = httpHeaders.build(); MockResponse peek = dispatcher.peek(); - if (!readBody && peek.getSocketPolicy() == EXPECT_CONTINUE) { - stream.writeHeaders(Collections.singletonList( - new Header(Header.RESPONSE_STATUS, ByteString.encodeUtf8("100 Continue"))), true); + if (peek.isDuplex()) { + readBody = false; + } else if (!readBody && peek.getSocketPolicy() == EXPECT_CONTINUE) { + List<Header> continueHeaders = Collections.singletonList( + new Header(Header.RESPONSE_STATUS, ByteString.encodeUtf8("100 Continue"))); + stream.writeHeaders(continueHeaders, false, true); stream.getConnection().flush(); readBody = true; } @@ -984,7 +1010,8 @@ public final class MockWebServer extends ExternalResource implements Closeable { sequenceNumber.getAndIncrement(), socket); } - private void writeResponse(Http2Stream stream, MockResponse response) throws IOException { + private void writeResponse(final Http2Stream stream, + final RecordedRequest request, final MockResponse response) throws IOException { Settings settings = response.getSettings(); if (settings != null) { stream.getConnection().setSettings(settings); @@ -1004,24 +1031,41 @@ public final class MockWebServer extends ExternalResource implements Closeable { for (int i = 0, size = headers.size(); i < size; i++) { http2Headers.add(new Header(headers.name(i), headers.value(i))); } + Headers trailers = response.getTrailers(); sleepIfDelayed(response.getHeadersDelay(TimeUnit.MILLISECONDS)); Buffer body = response.getBody(); - boolean closeStreamAfterHeaders = body != null || !response.getPushPromises().isEmpty(); - stream.writeHeaders(http2Headers, closeStreamAfterHeaders); - pushPromises(stream, response.getPushPromises()); + boolean outFinished = body == null + && response.getPushPromises().isEmpty() + && !response.isDuplex(); + boolean flushHeaders = body == null; + if (outFinished && trailers.size() > 0) { + throw new IllegalStateException("unsupported: no body and non-empty trailers " + trailers); + } + stream.writeHeaders(http2Headers, outFinished, flushHeaders); + if (trailers.size() > 0) { + stream.enqueueTrailers(trailers); + } + pushPromises(stream, request, response.getPushPromises()); if (body != null) { - BufferedSink sink = Okio.buffer(stream.getSink()); - sleepIfDelayed(response.getBodyDelay(TimeUnit.MILLISECONDS)); - throttledTransfer(response, socket, body, sink, body.size(), false); - sink.close(); - } else if (closeStreamAfterHeaders) { + try (BufferedSink sink = Okio.buffer(stream.getSink())) { + sleepIfDelayed(response.getBodyDelay(TimeUnit.MILLISECONDS)); + throttledTransfer(response, socket, body, sink, body.size(), false); + } + } else if (response.isDuplex()) { + try (BufferedSink sink = Okio.buffer(stream.getSink()); + BufferedSource source = Okio.buffer(stream.getSource())) { + DuplexResponseBody duplexResponseBody = response.getDuplexResponseBody(); + duplexResponseBody.onRequest(request, source, sink); + } + } else if (!outFinished) { stream.close(ErrorCode.NO_ERROR); } } - private void pushPromises(Http2Stream stream, List<PushPromise> promises) throws IOException { + private void pushPromises(Http2Stream stream, RecordedRequest request, + List<PushPromise> promises) throws IOException { for (PushPromise pushPromise : promises) { List<Header> pushedHeaders = new ArrayList<>(); pushedHeaders.add(new Header(Header.TARGET_AUTHORITY, url(pushPromise.path()).host())); @@ -1038,7 +1082,7 @@ public final class MockWebServer extends ExternalResource implements Closeable { boolean hasBody = pushPromise.response().getBody() != null; Http2Stream pushedStream = stream.getConnection().pushStream(stream.getId(), pushedHeaders, hasBody); - writeResponse(pushedStream, pushPromise.response()); + writeResponse(pushedStream, request, pushPromise.response()); } } } diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/HttpClient.java b/mockwebserver/src/main/java/okhttp3/mockwebserver/internal/duplex/DuplexResponseBody.java similarity index 60% rename from benchmarks/src/main/java/okhttp3/benchmarks/HttpClient.java rename to mockwebserver/src/main/java/okhttp3/mockwebserver/internal/duplex/DuplexResponseBody.java index 0c0986ffeb2e454931ecf54534561c04b946cabe..79a8245e8fd4163c5d0cdcee22f22bbbf437d8f6 100644 --- a/benchmarks/src/main/java/okhttp3/benchmarks/HttpClient.java +++ b/mockwebserver/src/main/java/okhttp3/mockwebserver/internal/duplex/DuplexResponseBody.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Square, Inc. + * Copyright (C) 2018 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package okhttp3.benchmarks; +package okhttp3.mockwebserver.internal.duplex; -import okhttp3.HttpUrl; +import java.io.IOException; +import okhttp3.mockwebserver.RecordedRequest; +import okio.BufferedSink; +import okio.BufferedSource; -/** An HTTP client to benchmark. */ -interface HttpClient { - void prepare(Benchmark benchmark); - - void enqueue(HttpUrl url) throws Exception; - - boolean acceptingJobs(); +public interface DuplexResponseBody { + void onRequest(RecordedRequest request, BufferedSource requestBody, BufferedSink responseBody) + throws IOException; } diff --git a/mockwebserver/src/main/java/okhttp3/mockwebserver/internal/duplex/MockDuplexResponseBody.java b/mockwebserver/src/main/java/okhttp3/mockwebserver/internal/duplex/MockDuplexResponseBody.java new file mode 100644 index 0000000000000000000000000000000000000000..7be8b934b20e231570b567af3ceaf187f2e70e5d --- /dev/null +++ b/mockwebserver/src/main/java/okhttp3/mockwebserver/internal/duplex/MockDuplexResponseBody.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2019 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package okhttp3.mockwebserver.internal.duplex; + +import java.io.IOException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.FutureTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import okhttp3.mockwebserver.RecordedRequest; +import okio.BufferedSink; +import okio.BufferedSource; +import okio.Utf8; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * A scriptable request/response conversation. Create the script by calling methods like {@link + * #receiveRequest} in the sequence they are run. + */ +public final class MockDuplexResponseBody implements DuplexResponseBody { + private final BlockingQueue<Action> actions = new LinkedBlockingQueue<>(); + private final BlockingQueue<FutureTask<Void>> results = new LinkedBlockingQueue<>(); + + public MockDuplexResponseBody receiveRequest(String expected) { + actions.add((request, requestBody, responseBody) -> { + assertEquals(expected, requestBody.readUtf8(Utf8.size(expected))); + }); + return this; + } + + public MockDuplexResponseBody exhaustRequest() { + actions.add((request, requestBody, responseBody) -> { + assertTrue(requestBody.exhausted()); + }); + return this; + } + + public MockDuplexResponseBody sendResponse(String s) { + actions.add((request, requestBody, responseBody) -> { + responseBody.writeUtf8(s); + responseBody.flush(); + }); + return this; + } + + public MockDuplexResponseBody exhaustResponse() { + actions.add((request, requestBody, responseBody) -> { + responseBody.close(); + }); + return this; + } + + @Override public void onRequest(RecordedRequest request, BufferedSource requestBody, + BufferedSink responseBody) { + FutureTask<Void> futureTask = new FutureTask<>(() -> { + for (Action action; (action = actions.poll()) != null; ) { + action.execute(request, requestBody, responseBody); + } + return null; // Success! + }); + results.add(futureTask); + futureTask.run(); + } + + /** Returns once the duplex conversation completes successfully. */ + public void awaitSuccess() throws Exception { + FutureTask<Void> futureTask = results.poll(5, TimeUnit.SECONDS); + if (futureTask == null) throw new AssertionError("no onRequest call received"); + futureTask.get(5, TimeUnit.SECONDS); + } + + private interface Action { + void execute(RecordedRequest request, BufferedSource requestBody, BufferedSink responseBody) + throws IOException; + } +} + diff --git a/mockwebserver/src/test/java/okhttp3/mockwebserver/CustomDispatcherTest.java b/mockwebserver/src/test/java/okhttp3/mockwebserver/CustomDispatcherTest.java index ec8317e95e9b9e729727fafe47014bc694af441d..0a43dd9165fb002df8809fa453d49a73275f2dc4 100644 --- a/mockwebserver/src/test/java/okhttp3/mockwebserver/CustomDispatcherTest.java +++ b/mockwebserver/src/test/java/okhttp3/mockwebserver/CustomDispatcherTest.java @@ -83,16 +83,14 @@ public class CustomDispatcherTest { assertEquals(200, secondResponseCode.get()); // (Still done). } - private Thread buildRequestThread(final String path, final AtomicInteger responseCode) { - return new Thread(new Runnable() { - @Override public void run() { - final URL url = mockWebServer.url(path).url(); - final HttpURLConnection conn; - try { - conn = (HttpURLConnection) url.openConnection(); - responseCode.set(conn.getResponseCode()); // Force the connection to hit the "server". - } catch (IOException e) { - } + private Thread buildRequestThread(String path, AtomicInteger responseCode) { + return new Thread(() -> { + URL url = mockWebServer.url(path).url(); + HttpURLConnection conn; + try { + conn = (HttpURLConnection) url.openConnection(); + responseCode.set(conn.getResponseCode()); // Force the connection to hit the "server". + } catch (IOException ignored) { } }); } diff --git a/mockwebserver/src/test/java/okhttp3/mockwebserver/MockWebServerTest.java b/mockwebserver/src/test/java/okhttp3/mockwebserver/MockWebServerTest.java index eb41f4fbcaed2993cf054b2d0e4c4e164cd2887e..67f417e73f01bff29c13e8f470ea0f7916ee012a 100644 --- a/mockwebserver/src/test/java/okhttp3/mockwebserver/MockWebServerTest.java +++ b/mockwebserver/src/test/java/okhttp3/mockwebserver/MockWebServerTest.java @@ -27,7 +27,6 @@ import java.net.ProtocolException; import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLConnection; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -40,14 +39,15 @@ import okhttp3.HttpUrl; import okhttp3.Protocol; import okhttp3.RecordingHostnameVerifier; import okhttp3.internal.Util; -import okhttp3.tls.HeldCertificate; import okhttp3.tls.HandshakeCertificates; +import okhttp3.tls.HeldCertificate; import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.runner.Description; import org.junit.runners.model.Statement; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static okhttp3.tls.internal.TlsUtil.localhost; @@ -165,15 +165,13 @@ public final class MockWebServerTest { * response is ready. */ @Test public void dispatchBlocksWaitingForEnqueue() throws Exception { - new Thread() { - @Override public void run() { - try { - Thread.sleep(1000); - } catch (InterruptedException ignored) { - } - server.enqueue(new MockResponse().setBody("enqueued in the background")); + new Thread(() -> { + try { + Thread.sleep(1000); + } catch (InterruptedException ignored) { } - }.start(); + server.enqueue(new MockResponse().setBody("enqueued in the background")); + }).start(); URLConnection connection = server.url("/").url().openConnection(); InputStream in = connection.getInputStream(); @@ -248,7 +246,7 @@ public final class MockWebServerTest { long startNanos = System.nanoTime(); URLConnection connection = server.url("/").url().openConnection(); connection.setDoOutput(true); - connection.getOutputStream().write("ABCDEF".getBytes("UTF-8")); + connection.getOutputStream().write("ABCDEF".getBytes(UTF_8)); InputStream in = connection.getInputStream(); assertEquals(-1, in.read()); long elapsedNanos = System.nanoTime() - startNanos; @@ -477,7 +475,7 @@ public final class MockWebServerTest { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoOutput(true); connection.setRequestProperty("Expect", "100-Continue"); - connection.getOutputStream().write("request".getBytes(StandardCharsets.UTF_8)); + connection.getOutputStream().write("request".getBytes(UTF_8)); InputStream in = connection.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); diff --git a/mockwebserver/src/test/java/okhttp3/mockwebserver/RecordedRequestTest.java b/mockwebserver/src/test/java/okhttp3/mockwebserver/RecordedRequestTest.java index e74e3c527545bca4930eb4f86b837508718ff2ef..acd37905f9cc0e39eb4c40bb5b1de869b592bc07 100644 --- a/mockwebserver/src/test/java/okhttp3/mockwebserver/RecordedRequestTest.java +++ b/mockwebserver/src/test/java/okhttp3/mockwebserver/RecordedRequestTest.java @@ -18,20 +18,18 @@ package okhttp3.mockwebserver; import java.net.Inet4Address; import java.net.InetAddress; -import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; import java.util.Collections; -import java.util.Random; import okhttp3.Headers; -import okhttp3.HttpUrl; +import okhttp3.internal.Util; import okio.Buffer; import org.junit.Test; import static org.junit.Assert.assertEquals; public class RecordedRequestTest { - Headers headers = new Headers.Builder().build(); + Headers headers = Util.EMPTY_HEADERS; private class FakeSocket extends Socket { private final InetAddress localAddress; @@ -75,20 +73,18 @@ public class RecordedRequestTest { Socket socket = new FakeSocket(InetAddress.getByAddress("127.0.0.1", new byte[] { 127, 0, 0, 1 }), 80); - RecordedRequest request = - new RecordedRequest("GET / HTTP/1.1", headers, Collections.<Integer>emptyList(), 0, - new Buffer(), 0, socket); + RecordedRequest request = new RecordedRequest("GET / HTTP/1.1", headers, + Collections.emptyList(), 0, new Buffer(), 0, socket); assertEquals("http://127.0.0.1/", request.getRequestUrl().toString()); } - @Test public void testIPv6() throws UnknownHostException { + @Test public void testIpv6() throws UnknownHostException { Socket socket = new FakeSocket(InetAddress.getByAddress("::1", new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }), 80); - RecordedRequest request = - new RecordedRequest("GET / HTTP/1.1", headers, Collections.<Integer>emptyList(), 0, - new Buffer(), 0, socket); + RecordedRequest request = new RecordedRequest("GET / HTTP/1.1", headers, + Collections.emptyList(), 0, new Buffer(), 0, socket); assertEquals("http://[::1]/", request.getRequestUrl().toString()); } @@ -97,9 +93,8 @@ public class RecordedRequestTest { Socket socket = new FakeSocket(InetAddress.getByAddress("127.0.0.1", new byte[] { 127, 0, 0, 1 }), 80); - RecordedRequest request = - new RecordedRequest("GET / HTTP/1.1", headers, Collections.<Integer>emptyList(), 0, - new Buffer(), 0, socket); + RecordedRequest request = new RecordedRequest("GET / HTTP/1.1", headers, + Collections.emptyList(), 0, new Buffer(), 0, socket); assertEquals("http://127.0.0.1/", request.getRequestUrl().toString()); } diff --git a/mockwebserver/src/test/java/okhttp3/mockwebserver/internal/http2/Http2Server.java b/mockwebserver/src/test/java/okhttp3/mockwebserver/internal/http2/Http2Server.java index 7d3b09a2cbc9697a6c6db9642d6b7b260b7dd3f5..b7ca4b248872546d79e75493964e3ce5ea3119d5 100644 --- a/mockwebserver/src/test/java/okhttp3/mockwebserver/internal/http2/Http2Server.java +++ b/mockwebserver/src/test/java/okhttp3/mockwebserver/internal/http2/Http2Server.java @@ -130,7 +130,7 @@ public final class Http2Server extends Http2Connection.Listener { new Header(":version", "HTTP/1.1"), new Header("content-type", "text/plain") ); - stream.writeHeaders(responseHeaders, true); + stream.writeHeaders(responseHeaders, false, false); BufferedSink out = Okio.buffer(stream.getSink()); out.writeUtf8("Not found: " + path); out.close(); @@ -142,7 +142,7 @@ public final class Http2Server extends Http2Connection.Listener { new Header(":version", "HTTP/1.1"), new Header("content-type", "text/html; charset=UTF-8") ); - stream.writeHeaders(responseHeaders, true); + stream.writeHeaders(responseHeaders, false, false); BufferedSink out = Okio.buffer(stream.getSink()); for (File file : files) { String target = file.isDirectory() ? (file.getName() + "/") : file.getName(); @@ -157,14 +157,9 @@ public final class Http2Server extends Http2Connection.Listener { new Header(":version", "HTTP/1.1"), new Header("content-type", contentType(file)) ); - stream.writeHeaders(responseHeaders, true); - Source source = Okio.source(file); - try { - BufferedSink out = Okio.buffer(stream.getSink()); - out.writeAll(source); - out.close(); - } finally { - Util.closeQuietly(source); + stream.writeHeaders(responseHeaders, false, false); + try (Source source = Okio.source(file); BufferedSink sink = Okio.buffer(stream.getSink())) { + sink.writeAll(source); } } diff --git a/okcurl/pom.xml b/okcurl/pom.xml index 1048a3b25ccf4595e0bdf792e4df784789ca8b7b..54b92c6aa99002453ec5d321e1da6356f230090a 100644 --- a/okcurl/pom.xml +++ b/okcurl/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3</groupId> <artifactId>parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>okcurl</artifactId> @@ -57,7 +57,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> - <version>3.0.0</version> + <version>3.1.0</version> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> @@ -80,7 +80,7 @@ <plugin> <groupId>org.skife.maven</groupId> <artifactId>really-executable-jar-maven-plugin</artifactId> - <version>1.1.0</version> + <version>1.5.0</version> <executions> <execution> <phase>package</phase> @@ -96,7 +96,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.1.1</version> <configuration> <archive> <manifestEntries> diff --git a/okcurl/src/main/java/okhttp3/curl/Main.java b/okcurl/src/main/java/okhttp3/curl/Main.java index 4b1fd210871e1a21cbcff32f46b04a39eb9aa771..22c8b5c45ca9314df14d8261edd91ddf155075e0 100644 --- a/okcurl/src/main/java/okhttp3/curl/Main.java +++ b/okcurl/src/main/java/okhttp3/curl/Main.java @@ -33,7 +33,6 @@ import java.util.logging.Logger; import java.util.logging.SimpleFormatter; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; @@ -104,6 +103,11 @@ public class Main extends HelpOption implements Runnable { @Option(name = "--read-timeout", description = "Maximum time allowed for reading data (seconds)") public int readTimeout = DEFAULT_TIMEOUT; + @Option( + name = "--call-timeout", + description = "Maximum time allowed for the entire call (seconds)") + public int callTimeout = DEFAULT_TIMEOUT; + @Option(name = {"-L", "--location"}, description = "Follow redirects") public boolean followRedirects; @@ -164,7 +168,7 @@ public class Main extends HelpOption implements Runnable { Sink out = Okio.sink(System.out); BufferedSource source = response.body().source(); while (!source.exhausted()) { - out.write(source.buffer(), source.buffer().size()); + out.write(source.getBuffer(), source.getBuffer().size()); out.flush(); } @@ -185,6 +189,9 @@ public class Main extends HelpOption implements Runnable { if (readTimeout != DEFAULT_TIMEOUT) { builder.readTimeout(readTimeout, SECONDS); } + if (callTimeout != DEFAULT_TIMEOUT) { + builder.callTimeout(callTimeout, SECONDS); + } if (allowInsecure) { X509TrustManager trustManager = createInsecureTrustManager(); SSLSocketFactory sslSocketFactory = createInsecureSslSocketFactory(trustManager); @@ -192,13 +199,7 @@ public class Main extends HelpOption implements Runnable { builder.hostnameVerifier(createInsecureHostnameVerifier()); } if (verbose) { - HttpLoggingInterceptor.Logger logger = - new HttpLoggingInterceptor.Logger() { - @Override - public void log(String message) { - System.out.println(message); - } - }; + HttpLoggingInterceptor.Logger logger = System.out::println; builder.eventListenerFactory(new LoggingEventListener.Factory(logger)); } return builder.build(); @@ -284,11 +285,7 @@ public class Main extends HelpOption implements Runnable { } private static HostnameVerifier createInsecureHostnameVerifier() { - return new HostnameVerifier() { - @Override public boolean verify(String s, SSLSession sslSession) { - return true; - } - }; + return (name, session) -> true; } private static void enableHttp2FrameLogging() { diff --git a/okhttp-android-support/pom.xml b/okhttp-android-support/pom.xml index 28128731b7b8d810644f8e7dd3ac0decf4ed6d6e..47dce8502d9946109ab17e2dd4ce23be9326a558 100644 --- a/okhttp-android-support/pom.xml +++ b/okhttp-android-support/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3</groupId> <artifactId>parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>okhttp-android-support</artifactId> @@ -57,7 +57,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>2.10.4</version> + <version>3.0.1</version> <configuration> <excludePackageNames>okhttp3.internal.*</excludePackageNames> <links> @@ -68,7 +68,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.1.1</version> <configuration> <archive> <manifestEntries> diff --git a/okhttp-android-support/src/main/java/okhttp3/AndroidShimResponseCache.java b/okhttp-android-support/src/main/java/okhttp3/AndroidShimResponseCache.java index c46a0cb9fd771b29f613f4473beb952f2b72fdc9..a0590b2c4b578c2444b86eeacf0c101f79a34015 100644 --- a/okhttp-android-support/src/main/java/okhttp3/AndroidShimResponseCache.java +++ b/okhttp-android-support/src/main/java/okhttp3/AndroidShimResponseCache.java @@ -24,6 +24,7 @@ import java.net.URI; import java.net.URLConnection; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; import okhttp3.internal.huc.JavaApiConverter; /** @@ -64,7 +65,8 @@ public class AndroidShimResponseCache extends ResponseCache { return JavaApiConverter.createJavaCacheResponse(okResponse); } - @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException { + @Override public @Nullable CacheRequest put( + URI uri, URLConnection urlConnection) throws IOException { Response okResponse = JavaApiConverter.createOkResponseForCachePut(uri, urlConnection); if (okResponse == null) { // The URLConnection is not cacheable or could not be converted. Stop. diff --git a/okhttp-android-support/src/main/java/okhttp3/internal/huc/JavaApiConverter.java b/okhttp-android-support/src/main/java/okhttp3/internal/huc/JavaApiConverter.java index 1dec85cdc0d562669a4cb9bfd57015ddd3fc5334..fa0236a3591975b4b9ae197cee8bae86ad271bde 100644 --- a/okhttp-android-support/src/main/java/okhttp3/internal/huc/JavaApiConverter.java +++ b/okhttp-android-support/src/main/java/okhttp3/internal/huc/JavaApiConverter.java @@ -167,7 +167,7 @@ public final class JavaApiConverter { } Set<String> varyFields = HttpHeaders.varyFields(responseHeaders); if (varyFields.isEmpty()) { - return new Headers.Builder().build(); + return Util.EMPTY_HEADERS; } // This probably indicates another HTTP stack is trying to use the shared ResponseCache. @@ -213,7 +213,7 @@ public final class JavaApiConverter { Headers varyHeaders; if (HttpHeaders.hasVaryAll(responseHeaders)) { // "*" means that this will be treated as uncacheable anyway. - varyHeaders = new Headers.Builder().build(); + varyHeaders = Util.EMPTY_HEADERS; } else { varyHeaders = HttpHeaders.varyHeaders(request.headers(), responseHeaders); } @@ -878,7 +878,7 @@ public final class JavaApiConverter { } private static <T> List<T> nullSafeImmutableList(T[] elements) { - return elements == null ? Collections.<T>emptyList() : Util.immutableList(elements); + return elements == null ? Collections.emptyList() : Util.immutableList(elements); } private static long stringToLong(String s) { diff --git a/okhttp-android-support/src/test/java/okhttp3/AbstractResponseCache.java b/okhttp-android-support/src/test/java/okhttp3/AbstractResponseCache.java index d755487c92af0a145ca12b815829129f92172217..e5ce90dd34f8fcb8a35b5467b53530046950465a 100644 --- a/okhttp-android-support/src/test/java/okhttp3/AbstractResponseCache.java +++ b/okhttp-android-support/src/test/java/okhttp3/AbstractResponseCache.java @@ -25,14 +25,16 @@ import java.net.URL; import java.net.URLConnection; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; public class AbstractResponseCache extends ResponseCache { - @Override public CacheResponse get(URI uri, String requestMethod, + @Override public @Nullable CacheResponse get(URI uri, String requestMethod, Map<String, List<String>> requestHeaders) throws IOException { return null; } - @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException { + @Override public @Nullable CacheRequest put( + URI uri, URLConnection connection) throws IOException { return null; } diff --git a/okhttp-android-support/src/test/java/okhttp3/internal/huc/CacheAdapterTest.java b/okhttp-android-support/src/test/java/okhttp3/internal/huc/CacheAdapterTest.java index 09a438807f77cca41c0ac198d2d398007f2d5e06..c1b4a6f4d2813fb67520e6a54db574bd8e5ed963 100644 --- a/okhttp-android-support/src/test/java/okhttp3/internal/huc/CacheAdapterTest.java +++ b/okhttp-android-support/src/test/java/okhttp3/internal/huc/CacheAdapterTest.java @@ -23,7 +23,6 @@ import java.net.ResponseCache; import java.net.URI; import java.net.URL; import java.net.URLConnection; -import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.Map; @@ -43,6 +42,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import static java.nio.charset.StandardCharsets.UTF_8; import static okhttp3.tls.internal.TlsUtil.localhost; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -137,7 +137,7 @@ public class CacheAdapterTest { @Test public void put_httpGet() throws Exception { final String statusLine = "HTTP/1.1 200 Fantastic"; - final byte[] response = "ResponseString".getBytes(StandardCharsets.UTF_8); + final byte[] response = "ResponseString".getBytes(UTF_8); final URL serverUrl = configureServer( new MockResponse() .setStatus(statusLine) diff --git a/okhttp-android-support/src/test/java/okhttp3/internal/huc/JavaApiConverterTest.java b/okhttp-android-support/src/test/java/okhttp3/internal/huc/JavaApiConverterTest.java index ad5d3fdfd351e0bdf7d3eab498dfb37c8484695f..8acbe31519add6f1a451b5f2e3ce96ae9a1a470a 100644 --- a/okhttp-android-support/src/test/java/okhttp3/internal/huc/JavaApiConverterTest.java +++ b/okhttp-android-support/src/test/java/okhttp3/internal/huc/JavaApiConverterTest.java @@ -23,7 +23,6 @@ import java.net.CacheResponse; import java.net.HttpURLConnection; import java.net.SecureCacheResponse; import java.net.URI; -import java.nio.charset.StandardCharsets; import java.security.Principal; import java.security.cert.Certificate; import java.security.cert.CertificateException; @@ -49,7 +48,6 @@ import okhttp3.Response; import okhttp3.ResponseBody; import okhttp3.TlsVersion; import okhttp3.internal.Internal; -import okhttp3.internal.Util; import okhttp3.mockwebserver.MockWebServer; import okio.Buffer; import okio.BufferedSource; @@ -57,6 +55,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -114,7 +113,7 @@ public class JavaApiConverterTest { } @Override public InputStream getBody() throws IOException { - return new ByteArrayInputStream("HelloWorld".getBytes(StandardCharsets.UTF_8)); + return new ByteArrayInputStream("HelloWorld".getBytes(UTF_8)); } }; @@ -160,9 +159,9 @@ public class JavaApiConverterTest { @Test public void createOkResponseForCacheGet_secure() throws Exception { final String statusLine = "HTTP/1.1 200 Fantastic"; final Principal localPrincipal = LOCAL_CERT.getSubjectX500Principal(); - final List<Certificate> localCertificates = Arrays.<Certificate>asList(LOCAL_CERT); + final List<Certificate> localCertificates = Arrays.asList(LOCAL_CERT); final Principal serverPrincipal = SERVER_CERT.getSubjectX500Principal(); - final List<Certificate> serverCertificates = Arrays.<Certificate>asList(SERVER_CERT); + final List<Certificate> serverCertificates = Arrays.asList(SERVER_CERT); URI uri = new URI("https://foo/bar"); Request request = new Request.Builder().url(uri.toURL()).build(); SecureCacheResponse cacheResponse = new SecureCacheResponse() { @@ -174,7 +173,7 @@ public class JavaApiConverterTest { } @Override public InputStream getBody() throws IOException { - return new ByteArrayInputStream("HelloWorld".getBytes(StandardCharsets.UTF_8)); + return new ByteArrayInputStream("HelloWorld".getBytes(UTF_8)); } @Override public String getCipherSuite() { @@ -400,7 +399,7 @@ public class JavaApiConverterTest { } // Check retrieval of headers by index. - assertEquals(null, httpUrlConnection.getHeaderFieldKey(0)); + assertNull(httpUrlConnection.getHeaderFieldKey(0)); assertEquals("HTTP/1.1 200 Fantastic", httpUrlConnection.getHeaderField(0)); // After header zero there may be additional entries provided at the beginning or end by the // implementation. It's probably important that the relative ordering of the headers is @@ -466,7 +465,7 @@ public class JavaApiConverterTest { .url("https://secure/request") .build(); Handshake handshake = Handshake.get(TlsVersion.SSL_3_0, CipherSuite.TLS_RSA_WITH_NULL_MD5, - Arrays.<Certificate>asList(SERVER_CERT), Arrays.<Certificate>asList(LOCAL_CERT)); + Arrays.asList(SERVER_CERT), Arrays.asList(LOCAL_CERT)); Response okResponse = createArbitraryOkResponse(okRequest).newBuilder() .handshake(handshake) .build(); @@ -556,7 +555,7 @@ public class JavaApiConverterTest { .build(); ResponseBody responseBody = createResponseBody("ResponseBody"); Handshake handshake = Handshake.get(TlsVersion.SSL_3_0, CipherSuite.TLS_RSA_WITH_NULL_MD5, - Arrays.<Certificate>asList(SERVER_CERT), Arrays.<Certificate>asList(LOCAL_CERT)); + Arrays.asList(SERVER_CERT), Arrays.asList(LOCAL_CERT)); Response okResponse = createArbitraryOkResponse(okRequest).newBuilder() .protocol(Protocol.HTTP_1_1) .code(200) @@ -612,7 +611,7 @@ public class JavaApiConverterTest { assertEquals("StatusLine", JavaApiConverter.extractStatusLine(javaResponseHeaders)); try { - JavaApiConverter.extractStatusLine(Collections.<String, List<String>>emptyMap()); + JavaApiConverter.extractStatusLine(Collections.emptyMap()); fail(); } catch (IOException expected) { } @@ -626,7 +625,7 @@ public class JavaApiConverterTest { private static X509Certificate certificate(String certificate) { try { return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate( - new ByteArrayInputStream(certificate.getBytes(Util.UTF_8))); + new ByteArrayInputStream(certificate.getBytes(UTF_8))); } catch (CertificateException e) { fail(); return null; diff --git a/okhttp-android-support/src/test/java/okhttp3/internal/huc/ResponseCacheTest.java b/okhttp-android-support/src/test/java/okhttp3/internal/huc/ResponseCacheTest.java index eb95e3ea4ecb07a19f07f8901e2ef2c08363d244..94c875d814e55ac366b10b4cac5c2c727d14a67d 100644 --- a/okhttp-android-support/src/test/java/okhttp3/internal/huc/ResponseCacheTest.java +++ b/okhttp-android-support/src/test/java/okhttp3/internal/huc/ResponseCacheTest.java @@ -16,12 +16,10 @@ package okhttp3.internal.huc; -import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; import java.net.CacheRequest; import java.net.CacheResponse; @@ -67,6 +65,7 @@ import okhttp3.mockwebserver.SocketPolicy; import okhttp3.tls.HandshakeCertificates; import okio.Buffer; import okio.BufferedSink; +import okio.BufferedSource; import okio.GzipSink; import okio.Okio; import org.junit.After; @@ -493,15 +492,15 @@ public final class ResponseCacheTest { server.enqueue(new MockResponse() .setBody("Request #2")); - BufferedReader reader = new BufferedReader( - new InputStreamReader(openConnection(server.url("/").url()).getInputStream())); - assertEquals("ABCDE", reader.readLine()); + BufferedSource bodySource = Okio.buffer(Okio.source( + openConnection(server.url("/").url()).getInputStream())); + assertEquals("ABCDE\n", bodySource.readUtf8(6)); try { - reader.readLine(); + bodySource.readUtf8(21); fail("This implementation silently ignored a truncated HTTP body."); } catch (IOException expected) { } finally { - reader.close(); + bodySource.close(); } URLConnection connection = openConnection(server.url("/").url()); @@ -1543,7 +1542,7 @@ public final class ResponseCacheTest { URLConnection connection2 = openConnection(server.url("/").url()); assertEquals("A", readAscii(connection2)); - assertEquals(null, connection2.getHeaderField("Warning")); + assertNull(connection2.getHeaderField("Warning")); } @Test public void getHeadersRetainsCached200LevelWarnings() throws Exception { @@ -1600,7 +1599,7 @@ public final class ResponseCacheTest { // still valid HttpURLConnection connection1 = openConnection(server.url("/a").url()); assertEquals("A", readAscii(connection1)); - assertEquals(null, connection1.getHeaderField("Allow")); + assertNull(connection1.getHeaderField("Allow")); // conditional cache hit; The cached data should be returned, but the cache is not updated. HttpURLConnection connection2 = openConnection(server.url("/a").url()); @@ -1761,17 +1760,17 @@ public final class ResponseCacheTest { } enum TransferKind { - CHUNKED() { + CHUNKED { @Override void setBody(MockResponse response, Buffer content, int chunkSize) { response.setChunkedBody(content, chunkSize); } }, - FIXED_LENGTH() { + FIXED_LENGTH { @Override void setBody(MockResponse response, Buffer content, int chunkSize) { response.setBody(content); } }, - END_OF_STREAM() { + END_OF_STREAM { @Override void setBody(MockResponse response, Buffer content, int chunkSize) { response.setBody(content); response.setSocketPolicy(SocketPolicy.DISCONNECT_AT_END); diff --git a/okhttp-apache/pom.xml b/okhttp-apache/pom.xml index 2c2eeddd02bdcf198d17e4eea05abf3da12f4ed1..53424378cf62861b207aa613e0c442894d88cf3b 100644 --- a/okhttp-apache/pom.xml +++ b/okhttp-apache/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3</groupId> <artifactId>parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>okhttp-apache</artifactId> @@ -48,7 +48,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>2.10.4</version> + <version>3.0.1</version> <configuration> <links> <link>http://square.github.io/okhttp/javadoc/</link> @@ -60,7 +60,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.1.1</version> <configuration> <archive> <manifestEntries> diff --git a/okhttp-apache/src/test/java/okhttp3/apache/OkApacheClientTest.java b/okhttp-apache/src/test/java/okhttp3/apache/OkApacheClientTest.java index 77493a25409e6086e73c97c41c18dbad3554bf2c..f8482564ab93e1da6856c6bd7569e4cc3564c7ef 100644 --- a/okhttp-apache/src/test/java/okhttp3/apache/OkApacheClientTest.java +++ b/okhttp-apache/src/test/java/okhttp3/apache/OkApacheClientTest.java @@ -25,7 +25,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import static okhttp3.internal.Util.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; diff --git a/okhttp-dnsoverhttps/pom.xml b/okhttp-dnsoverhttps/pom.xml index 1c0a561d9ac1f810c55b291e2d6a727c76f9fd5c..6a6cccb2054e8e844a20a5118ec62d7b39ff3f6b 100644 --- a/okhttp-dnsoverhttps/pom.xml +++ b/okhttp-dnsoverhttps/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3</groupId> <artifactId>parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>okhttp-dnsoverhttps</artifactId> @@ -53,7 +53,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.1.1</version> <configuration> <archive> <manifestEntries> diff --git a/okhttp-hpacktests/pom.xml b/okhttp-hpacktests/pom.xml index 12bc4e4b4959bae5335876d82168f278ebc16727..a2a22ae3656e78a887032527ccb1d4c21b3f8a2e 100644 --- a/okhttp-hpacktests/pom.xml +++ b/okhttp-hpacktests/pom.xml @@ -8,7 +8,7 @@ <parent> <groupId>com.squareup.okhttp3</groupId> <artifactId>parent</artifactId> - <version>3.5.0-SNAPSHOT</version> + <version>3.13.0-SNAPSHOT</version> </parent> <artifactId>okhttp-hpacktests</artifactId> diff --git a/okhttp-logging-interceptor/pom.xml b/okhttp-logging-interceptor/pom.xml index 18d5722adb78e0b75f647f9f234b91d1c60b0645..173604128b9a70bcb535946e288515ff96b9db80 100644 --- a/okhttp-logging-interceptor/pom.xml +++ b/okhttp-logging-interceptor/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3</groupId> <artifactId>parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>logging-interceptor</artifactId> @@ -54,7 +54,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.1.1</version> <configuration> <archive> <manifestEntries> diff --git a/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java b/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java index c6acbccf1ebca6bc6c2b4e49535531882733e15e..1c960b74bd5a76071e8d382fbf06deee9e0f9b7b 100644 --- a/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java +++ b/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java @@ -109,11 +109,7 @@ public final class HttpLoggingInterceptor implements Interceptor { void log(String message); /** A {@link Logger} defaults output appropriate for the current platform. */ - Logger DEFAULT = new Logger() { - @Override public void log(String message) { - Platform.get().log(INFO, message, null); - } - }; + Logger DEFAULT = message -> Platform.get().log(INFO, message, null); } public HttpLoggingInterceptor() { @@ -251,20 +247,14 @@ public final class HttpLoggingInterceptor implements Interceptor { } else { BufferedSource source = responseBody.source(); source.request(Long.MAX_VALUE); // Buffer the entire body. - Buffer buffer = source.buffer(); + Buffer buffer = source.getBuffer(); Long gzippedLength = null; if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) { gzippedLength = buffer.size(); - GzipSource gzippedResponseBody = null; - try { - gzippedResponseBody = new GzipSource(buffer.clone()); + try (GzipSource gzippedResponseBody = new GzipSource(buffer.clone())) { buffer = new Buffer(); buffer.writeAll(gzippedResponseBody); - } finally { - if (gzippedResponseBody != null) { - gzippedResponseBody.close(); - } } } diff --git a/okhttp-logging-interceptor/src/main/java/okhttp3/logging/LoggingEventListener.java b/okhttp-logging-interceptor/src/main/java/okhttp3/logging/LoggingEventListener.java index cc5adde48e9a69466557bcfe9c475b0ed11eb336..da38261bfb07071926d20550457f432625ded95c 100644 --- a/okhttp-logging-interceptor/src/main/java/okhttp3/logging/LoggingEventListener.java +++ b/okhttp-logging-interceptor/src/main/java/okhttp3/logging/LoggingEventListener.java @@ -75,7 +75,7 @@ public final class LoggingEventListener extends EventListener { @Override public void secureConnectEnd(Call call, @Nullable Handshake handshake) { - logWithTime("secureConnectEnd"); + logWithTime("secureConnectEnd: " + handshake); } @Override diff --git a/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java b/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java index 6fd53f7753ee6f79a056c9b15c3fcdafb48a26d2..bd4b8f79bcdd99b43b449b803fa06ef2c6aabb56 100644 --- a/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java +++ b/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java @@ -16,14 +16,11 @@ package okhttp3.logging; import java.io.IOException; -import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; import javax.net.ssl.HostnameVerifier; -import okhttp3.Dns; import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -705,11 +702,7 @@ public final class HttpLoggingInterceptorTest { @Test public void connectFail() throws IOException { setLevel(Level.BASIC); client = new OkHttpClient.Builder() - .dns(new Dns() { - @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException { - throw new UnknownHostException("reason"); - } - }) + .dns(hostname -> { throw new UnknownHostException("reason"); }) .addInterceptor(applicationInterceptor) .build(); @@ -827,7 +820,7 @@ public final class HttpLoggingInterceptorTest { } void assertNoMoreLogs() { - assertTrue("More messages remain: " + logs.subList(index, logs.size()), index == logs.size()); + assertEquals("More messages remain: " + logs.subList(index, logs.size()), index, logs.size()); } @Override public void log(String message) { diff --git a/okhttp-logging-interceptor/src/test/java/okhttp3/logging/LoggingEventListenerTest.java b/okhttp-logging-interceptor/src/test/java/okhttp3/logging/LoggingEventListenerTest.java index 0f0ea2bd715e4db3c0c52d508584e74cd9052d1a..84aecb571c24f5bb1c9fe1f343a49a64b294ca99 100644 --- a/okhttp-logging-interceptor/src/test/java/okhttp3/logging/LoggingEventListenerTest.java +++ b/okhttp-logging-interceptor/src/test/java/okhttp3/logging/LoggingEventListenerTest.java @@ -16,10 +16,7 @@ package okhttp3.logging; import java.io.IOException; -import java.net.InetAddress; import java.net.UnknownHostException; -import java.util.List; -import okhttp3.Dns; import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -145,7 +142,11 @@ public final class LoggingEventListenerTest { .assertLogMatch("dnsEnd: \\[.+\\]") .assertLogMatch("connectStart: " + url.host() + "/.+ DIRECT") .assertLogMatch("secureConnectStart") - .assertLogMatch("secureConnectEnd") + .assertLogMatch("secureConnectEnd: Handshake\\{" + + "tlsVersion=TLS_1_2 " + + "cipherSuite=TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 " + + "peerCertificates=\\[CN=localhost\\] " + + "localCertificates=\\[\\]}") .assertLogMatch("connectEnd: h2") .assertLogMatch( "connectionAcquired: Connection\\{" @@ -167,17 +168,10 @@ public final class LoggingEventListenerTest { @Test public void dnsFail() throws IOException { - client = - new OkHttpClient.Builder() - .dns( - new Dns() { - @Override - public List<InetAddress> lookup(String hostname) throws UnknownHostException { - throw new UnknownHostException("reason"); - } - }) - .eventListenerFactory(loggingEventListenerFactory) - .build(); + client = new OkHttpClient.Builder() + .dns(hostname -> { throw new UnknownHostException("reason"); }) + .eventListenerFactory(loggingEventListenerFactory) + .build(); try { client.newCall(request().build()).execute(); diff --git a/okhttp-sse/pom.xml b/okhttp-sse/pom.xml index e2e64ca1f3285f4bad618f0342952a6343a2a200..82563b88d3639e740ac47267848bdbc603002956 100644 --- a/okhttp-sse/pom.xml +++ b/okhttp-sse/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3</groupId> <artifactId>parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>okhttp-sse</artifactId> @@ -18,11 +18,6 @@ <artifactId>okhttp</artifactId> <version>${project.version}</version> </dependency> - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>okhttp-testing-support</artifactId> - <version>${project.version}</version> - </dependency> <dependency> <groupId>com.google.code.findbugs</groupId> <artifactId>jsr305</artifactId> @@ -53,8 +48,9 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>2.10.4</version> + <version>3.0.1</version> <configuration> + <excludePackageNames>okhttp3.internal:okhttp3.internal.*</excludePackageNames> <links> <link>http://square.github.io/okhttp/javadoc/</link> </links> @@ -63,7 +59,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.1.1</version> <configuration> <archive> <manifestEntries> diff --git a/okhttp-sse/src/main/java/okhttp3/internal/sse/ServerSentEventReader.java b/okhttp-sse/src/main/java/okhttp3/internal/sse/ServerSentEventReader.java index 0e0e775fb646c07abb9a1cead255be0814201d4c..ae19d3745e4ab6bf96d3e7d06573d7c0efcf76a1 100644 --- a/okhttp-sse/src/main/java/okhttp3/internal/sse/ServerSentEventReader.java +++ b/okhttp-sse/src/main/java/okhttp3/internal/sse/ServerSentEventReader.java @@ -63,7 +63,7 @@ public final class ServerSentEventReader { return false; } - switch (source.buffer().getByte(0)) { + switch (source.getBuffer().getByte(0)) { case '\r': case '\n': completeEvent(id, type, data); @@ -162,7 +162,7 @@ public final class ServerSentEventReader { */ private boolean isKey(ByteString key) throws IOException { if (source.rangeEquals(0, key)) { - byte nextByte = source.buffer().getByte(key.size()); + byte nextByte = source.getBuffer().getByte(key.size()); return nextByte == ':' || nextByte == '\r' || nextByte == '\n'; @@ -174,7 +174,7 @@ public final class ServerSentEventReader { private void skipCrAndOrLf() throws IOException { if ((source.readByte() & 0xff) == '\r' && source.request(1) - && source.buffer().getByte(0) == '\n') { + && source.getBuffer().getByte(0) == '\n') { source.skip(1); } } @@ -186,11 +186,11 @@ public final class ServerSentEventReader { private long skipNameAndDivider(long length) throws IOException { source.skip(length); - if (source.buffer().getByte(0) == ':') { + if (source.getBuffer().getByte(0) == ':') { source.skip(1L); length++; - if (source.buffer().getByte(0) == ' ') { + if (source.getBuffer().getByte(0) == ' ') { source.skip(1); length++; } diff --git a/okhttp-sse/src/main/java/okhttp3/sse/EventSources.java b/okhttp-sse/src/main/java/okhttp3/sse/EventSources.java index 77f596c5f2657e9fc4d610aef1a6ee66f69dc69e..db0c818d54e78f9efa9d8b1e6538218d968580d9 100644 --- a/okhttp-sse/src/main/java/okhttp3/sse/EventSources.java +++ b/okhttp-sse/src/main/java/okhttp3/sse/EventSources.java @@ -16,18 +16,15 @@ package okhttp3.sse; import okhttp3.OkHttpClient; -import okhttp3.Request; import okhttp3.Response; import okhttp3.internal.sse.RealEventSource; public final class EventSources { public static EventSource.Factory createFactory(final OkHttpClient client) { - return new EventSource.Factory() { - @Override public EventSource newEventSource(Request request, EventSourceListener listener) { - RealEventSource eventSource = new RealEventSource(request, listener); - eventSource.connect(client); - return eventSource; - } + return (request, listener) -> { + RealEventSource eventSource = new RealEventSource(request, listener); + eventSource.connect(client); + return eventSource; }; } diff --git a/okhttp-sse/src/test/java/okhttp3/internal/sse/Event.java b/okhttp-sse/src/test/java/okhttp3/internal/sse/Event.java index 149be0014ce36413b017529047a3c14d93347ea5..fade2b978a2ffc609ecffdea69df5751f7b47216 100644 --- a/okhttp-sse/src/test/java/okhttp3/internal/sse/Event.java +++ b/okhttp-sse/src/test/java/okhttp3/internal/sse/Event.java @@ -15,6 +15,7 @@ */ package okhttp3.internal.sse; +import java.util.Objects; import javax.annotation.Nullable; final class Event { @@ -37,14 +38,14 @@ final class Event { if (this == o) return true; if (!(o instanceof Event)) return false; Event other = (Event) o; - return (id != null ? id.equals(other.id) : other.id == null) - && (type != null ? type.equals(other.type) : other.type == null) + return Objects.equals(id, other.id) + && Objects.equals(type, other.type) && data.equals(other.data); } @Override public int hashCode() { - int result = (id != null ? id.hashCode() : 0); - result = 31 * result + (type != null ? type.hashCode() : 0); + int result = Objects.hashCode(id); + result = 31 * result + Objects.hashCode(type); result = 31 * result + data.hashCode(); return result; } diff --git a/okhttp-testing-support/pom.xml b/okhttp-testing-support/pom.xml index 4ef7c3af17ae6bd5d5eb00612bfae0b4ad09df6b..763b92b42b9ecb9aa7013a068aa1188efb6aeae1 100644 --- a/okhttp-testing-support/pom.xml +++ b/okhttp-testing-support/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3</groupId> <artifactId>parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>okhttp-testing-support</artifactId> @@ -35,7 +35,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.1.1</version> <configuration> <archive> <manifestEntries> diff --git a/okhttp-testing-support/src/main/java/okhttp3/TestUtil.java b/okhttp-testing-support/src/main/java/okhttp3/TestUtil.java index 2a85617dc18d27e745963fe726173a0635f832e6..81b47ddf55502edd4a547ec154d590f9054d6e34 100644 --- a/okhttp-testing-support/src/main/java/okhttp3/TestUtil.java +++ b/okhttp-testing-support/src/main/java/okhttp3/TestUtil.java @@ -17,7 +17,6 @@ package okhttp3; import java.net.InetAddress; import java.net.InetSocketAddress; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -32,11 +31,9 @@ public final class TestUtil { * A network that resolves only one IP address per host. Use this when testing route selection * fallbacks to prevent the host machine's various IP addresses from interfering. */ - private static final Dns SINGLE_INET_ADDRESS_DNS = new Dns() { - @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException { - List<InetAddress> addresses = Dns.SYSTEM.lookup(hostname); - return Collections.singletonList(addresses.get(0)); - } + private static final Dns SINGLE_INET_ADDRESS_DNS = hostname -> { + List<InetAddress> addresses = Dns.SYSTEM.lookup(hostname); + return Collections.singletonList(addresses.get(0)); }; private TestUtil() { diff --git a/okhttp-testing-support/src/main/java/okhttp3/internal/duplex/AsyncRequestBody.java b/okhttp-testing-support/src/main/java/okhttp3/internal/duplex/AsyncRequestBody.java new file mode 100644 index 0000000000000000000000000000000000000000..1310423e6f45c7fce32c65bc1b933c1ec4c1a3f1 --- /dev/null +++ b/okhttp-testing-support/src/main/java/okhttp3/internal/duplex/AsyncRequestBody.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2019 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package okhttp3.internal.duplex; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import okio.BufferedSink; + +import static junit.framework.TestCase.assertTrue; + +/** A duplex request body that keeps the provided sinks so they can be written to later. */ +public final class AsyncRequestBody extends RequestBody implements DuplexRequestBody { + private final BlockingQueue<BufferedSink> requestBodySinks = new LinkedBlockingQueue<>(); + + @Override public @Nullable MediaType contentType() { + return null; + } + + @Override public void writeTo(BufferedSink sink) { + requestBodySinks.add(sink); + } + + public BufferedSink takeSink() throws InterruptedException { + BufferedSink result = requestBodySinks.poll(5, TimeUnit.SECONDS); + if (result == null) throw new AssertionError("no sink to take"); + return result; + } + + public void assertNoMoreSinks() { + assertTrue(requestBodySinks.isEmpty()); + } +} diff --git a/okhttp-testing-support/src/main/java/okhttp3/testing/InstallUncaughtExceptionHandlerListener.java b/okhttp-testing-support/src/main/java/okhttp3/testing/InstallUncaughtExceptionHandlerListener.java index ae034cd9a6013a06cf566b49b9d3b09b05718d64..c1768dcd4ba2624fa6aea314b07edcb5c8fd9e8b 100644 --- a/okhttp-testing-support/src/main/java/okhttp3/testing/InstallUncaughtExceptionHandlerListener.java +++ b/okhttp-testing-support/src/main/java/okhttp3/testing/InstallUncaughtExceptionHandlerListener.java @@ -38,24 +38,22 @@ public class InstallUncaughtExceptionHandlerListener extends RunListener { @Override public void testRunStarted(Description description) { System.err.println("Installing aggressive uncaught exception handler"); oldDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override public void uncaughtException(Thread thread, Throwable throwable) { - StringWriter errorText = new StringWriter(256); - errorText.append("Uncaught exception in OkHttp thread \""); - errorText.append(thread.getName()); - errorText.append("\"\n"); - throwable.printStackTrace(new PrintWriter(errorText)); + Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { + StringWriter errorText = new StringWriter(256); + errorText.append("Uncaught exception in OkHttp thread \""); + errorText.append(thread.getName()); + errorText.append("\"\n"); + throwable.printStackTrace(new PrintWriter(errorText)); + errorText.append("\n"); + if (lastTestStarted != null) { + errorText.append("Last test to start was: "); + errorText.append(lastTestStarted.getDisplayName()); errorText.append("\n"); - if (lastTestStarted != null) { - errorText.append("Last test to start was: "); - errorText.append(lastTestStarted.getDisplayName()); - errorText.append("\n"); - } - System.err.print(errorText.toString()); + } + System.err.print(errorText.toString()); - synchronized (exceptions) { - exceptions.put(throwable, lastTestStarted.getDisplayName()); - } + synchronized (exceptions) { + exceptions.put(throwable, lastTestStarted.getDisplayName()); } }); } diff --git a/okhttp-tests/pom.xml b/okhttp-tests/pom.xml index 06a589d22b43185dc7fc68991f2ee4ebcf06f5a1..daf88f42440388c5d96eb71ad7a707cc7bb14b8a 100644 --- a/okhttp-tests/pom.xml +++ b/okhttp-tests/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3</groupId> <artifactId>parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>okhttp-tests</artifactId> @@ -97,7 +97,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> - <version>2.7</version> + <version>2.8.2</version> <configuration> <skip>true</skip> </configuration> diff --git a/okhttp-tests/src/main/java/okhttp3/TestTls13Request.java b/okhttp-tests/src/main/java/okhttp3/TestTls13Request.java index dfe52799850aee9ce38b4060a6d285f144563b40..9e954b95fe99d3ac58b2e41c661df4e225d14d69 100644 --- a/okhttp-tests/src/main/java/okhttp3/TestTls13Request.java +++ b/okhttp-tests/src/main/java/okhttp3/TestTls13Request.java @@ -26,7 +26,6 @@ public class TestTls13Request { private static final ConnectionSpec TLS_13 = new ConnectionSpec.Builder(true) .cipherSuites(TLS13_CIPHER_SUITES) .tlsVersions(TlsVersion.TLS_1_3) - .supportsTlsExtensions(true) .build(); @@ -87,10 +86,7 @@ public class TestTls13Request { Request request = new Request.Builder().url(url).build(); - Response response = null; - try { - response = client.newCall(request).execute(); - + try (Response response = client.newCall(request).execute()) { Handshake handshake = response.handshake(); System.out.println(handshake.tlsVersion() + " " @@ -104,10 +100,6 @@ public class TestTls13Request { + "b"); } catch (IOException ioe) { System.out.println(ioe.toString()); - } finally { - if (response != null) { - response.close(); - } } } } diff --git a/okhttp-tests/src/test/java/okhttp3/AddressTest.java b/okhttp-tests/src/test/java/okhttp3/AddressTest.java index ad07b7824160c0404db324851bc8e73e77ca4e12..7a1b7f1bd104ac3a3955060230fe2be3547a6fb1 100644 --- a/okhttp-tests/src/test/java/okhttp3/AddressTest.java +++ b/okhttp-tests/src/test/java/okhttp3/AddressTest.java @@ -23,7 +23,7 @@ import okhttp3.internal.http.RecordingProxySelector; import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; public final class AddressTest { private Dns dns = Dns.SYSTEM; @@ -47,7 +47,7 @@ public final class AddressTest { authenticator, null, protocols, connectionSpecs, new RecordingProxySelector()); Address b = new Address("square.com", 80, dns, socketFactory, null, null, null, authenticator, null, protocols, connectionSpecs, new RecordingProxySelector()); - assertFalse(a.equals(b)); + assertNotEquals(a, b); } @Test public void addressToString() throws Exception { diff --git a/okhttp-tests/src/test/java/okhttp3/CacheTest.java b/okhttp-tests/src/test/java/okhttp3/CacheTest.java index 0d9ca9fa75f2cecb5ee06c4cabb572f7889f21aa..c75d5b4163da04f54b9c1cf594f9f149b5b088bb 100644 --- a/okhttp-tests/src/test/java/okhttp3/CacheTest.java +++ b/okhttp-tests/src/test/java/okhttp3/CacheTest.java @@ -34,7 +34,6 @@ import java.util.TimeZone; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSession; import okhttp3.internal.Internal; import okhttp3.internal.io.InMemoryFileSystem; import okhttp3.internal.platform.Platform; @@ -63,11 +62,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public final class CacheTest { - private static final HostnameVerifier NULL_HOSTNAME_VERIFIER = new HostnameVerifier() { - @Override public boolean verify(String s, SSLSession sslSession) { - return true; - } - }; + private static final HostnameVerifier NULL_HOSTNAME_VERIFIER = (name, session) -> true; @Rule public MockWebServer server = new MockWebServer(); @Rule public MockWebServer server2 = new MockWebServer(); @@ -524,7 +519,7 @@ public final class CacheTest { BufferedSource bodySource = get(server.url("/")).body().source(); assertEquals("ABCDE", bodySource.readUtf8Line()); try { - bodySource.readUtf8Line(); + bodySource.readUtf8(21); fail("This implementation silently ignored a truncated HTTP body."); } catch (IOException expected) { } finally { @@ -1865,7 +1860,7 @@ public final class CacheTest { Response response2 = get(server.url("/")); assertEquals("A", response2.body().string()); - assertEquals(null, response2.header("Warning")); + assertNull(response2.header("Warning")); } @Test public void getHeadersRetainsCached200LevelWarnings() throws Exception { @@ -1910,7 +1905,7 @@ public final class CacheTest { long t0 = System.currentTimeMillis(); Response response1 = get(server.url("/a")); assertEquals("A", response1.body().string()); - assertEquals(null, response1.header("Allow")); + assertNull(response1.header("Allow")); assertEquals(0, response1.receivedResponseAtMillis() - t0, 250.0); // A conditional cache hit updates the cache. @@ -2220,12 +2215,11 @@ public final class CacheTest { final AtomicReference<String> ifNoneMatch = new AtomicReference<>(); client = client.newBuilder() - .addNetworkInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - ifNoneMatch.compareAndSet(null, chain.request().header("If-None-Match")); - return chain.proceed(chain.request()); - } - }).build(); + .addNetworkInterceptor(chain -> { + ifNoneMatch.compareAndSet(null, chain.request().header("If-None-Match")); + return chain.proceed(chain.request()); + }) + .build(); // Confirm the value is cached and intercepted. assertEquals("A", get(url).body().string()); @@ -2243,11 +2237,8 @@ public final class CacheTest { // Confirm the interceptor isn't exercised. client = client.newBuilder() - .addNetworkInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - throw new AssertionError(); - } - }).build(); + .addNetworkInterceptor(chain -> { throw new AssertionError(); }) + .build(); assertEquals("A", get(url).body().string()); } @@ -2397,7 +2388,7 @@ public final class CacheTest { assertEquals("B", get(url).body().string()); assertEquals("B", get(url).body().string()); - assertEquals(null, server.takeRequest().getHeader("If-None-Match")); + assertNull(server.takeRequest().getHeader("If-None-Match")); assertEquals("v1", server.takeRequest().getHeader("If-None-Match")); assertEquals("v1", server.takeRequest().getHeader("If-None-Match")); assertEquals("v2", server.takeRequest().getHeader("If-None-Match")); @@ -2443,7 +2434,7 @@ public final class CacheTest { Response response2 = get(server.url("/")); assertEquals("abcd", response2.body().string()); - assertEquals(null, server.takeRequest().getHeader("If-None-Match")); + assertNull(server.takeRequest().getHeader("If-None-Match")); assertEquals("α", server.takeRequest().getHeader("If-None-Match")); } @@ -2586,18 +2577,17 @@ public final class CacheTest { } enum TransferKind { - CHUNKED() { - @Override void setBody(MockResponse response, Buffer content, int chunkSize) - throws IOException { + CHUNKED { + @Override void setBody(MockResponse response, Buffer content, int chunkSize) { response.setChunkedBody(content, chunkSize); } }, - FIXED_LENGTH() { + FIXED_LENGTH { @Override void setBody(MockResponse response, Buffer content, int chunkSize) { response.setBody(content); } }, - END_OF_STREAM() { + END_OF_STREAM { @Override void setBody(MockResponse response, Buffer content, int chunkSize) { response.setBody(content); response.setSocketPolicy(DISCONNECT_AT_END); diff --git a/okhttp-tests/src/test/java/okhttp3/CallTest.java b/okhttp-tests/src/test/java/okhttp3/CallTest.java index a58bc879c27b07503416b48192662c5caf151e48..88135fab441801d50bf2ddd9e1eff1f882f82fbf 100644 --- a/okhttp-tests/src/test/java/okhttp3/CallTest.java +++ b/okhttp-tests/src/test/java/okhttp3/CallTest.java @@ -37,7 +37,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -84,6 +83,7 @@ import org.junit.rules.TestRule; import org.junit.rules.Timeout; import static java.net.CookiePolicy.ACCEPT_ORIGINAL_SERVER; +import static okhttp3.CipherSuite.TLS_DH_anon_WITH_AES_128_GCM_SHA256; import static okhttp3.TestUtil.awaitGarbageCollection; import static okhttp3.TestUtil.defaultClient; import static okhttp3.tls.internal.TlsUtil.localhost; @@ -116,6 +116,10 @@ public final class CallTest { @After public void tearDown() throws Exception { cache.delete(); logger.removeHandler(logHandler); + + // Ensure the test has released all connections. + client.connectionPool().evictAll(); + assertEquals(0, client.connectionPool().connectionCount()); } @Test public void get() throws Exception { @@ -344,7 +348,7 @@ public final class CallTest { assertEquals("POST", recordedRequest.getMethod()); assertEquals(0, recordedRequest.getBody().size()); assertEquals("0", recordedRequest.getHeader("Content-Length")); - assertEquals(null, recordedRequest.getHeader("Content-Type")); + assertNull(recordedRequest.getHeader("Content-Type")); } @Test public void postZerolength_HTTPS() throws Exception { @@ -487,7 +491,7 @@ public final class CallTest { assertEquals("DELETE", recordedRequest.getMethod()); assertEquals(0, recordedRequest.getBody().size()); assertEquals("0", recordedRequest.getHeader("Content-Length")); - assertEquals(null, recordedRequest.getHeader("Content-Type")); + assertNull(recordedRequest.getHeader("Content-Type")); } @Test public void delete_HTTPS() throws Exception { @@ -605,7 +609,7 @@ public final class CallTest { executeSynchronously(request).assertCode(200); RecordedRequest recordedRequest = server.takeRequest(); - assertEquals(null, recordedRequest.getHeader("Content-Type")); + assertNull(recordedRequest.getHeader("Content-Type")); assertEquals("3", recordedRequest.getHeader("Content-Length")); assertEquals("abc", recordedRequest.getBody().readUtf8()); } @@ -669,6 +673,8 @@ public final class CallTest { } assertEquals("SyncApiTest", server.takeRequest().getHeader("User-Agent")); + + callback.await(request.url()).assertSuccessful(); } @Test public void legalToExecuteTwiceCloning() throws Exception { @@ -922,11 +928,7 @@ public final class CallTest { /** https://github.com/square/okhttp/issues/1801 */ @Test public void asyncCallEngineInitialized() throws Exception { OkHttpClient c = defaultClient().newBuilder() - .addInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - throw new IOException(); - } - }) + .addInterceptor(chain -> { throw new IOException(); }) .build(); Request request = new Request.Builder().url(server.url("/")).build(); c.newCall(request).enqueue(callback); @@ -1180,6 +1182,96 @@ public final class CallTest { } } + /** + * When the server doesn't present any certificates we fail the TLS handshake. This test requires + * that the client and server are each configured with a cipher suite that permits the server to + * be unauthenticated. + */ + @Test public void tlsSuccessWithNoPeerCertificates() throws Exception { + server.enqueue(new MockResponse() + .setBody("abc")); + + // The _anon_ cipher suites don't require server certificates. + CipherSuite cipherSuite = TLS_DH_anon_WITH_AES_128_GCM_SHA256; + + HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder() + .build(); + client = client.newBuilder() + .sslSocketFactory( + socketFactoryWithCipherSuite(clientCertificates.sslSocketFactory(), cipherSuite), + clientCertificates.trustManager()) + .connectionSpecs(Arrays.asList(new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) + .cipherSuites(cipherSuite) + .build())) + .hostnameVerifier(new RecordingHostnameVerifier()) + .build(); + + HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder() + .build(); + server.useHttps(socketFactoryWithCipherSuite( + serverCertificates.sslSocketFactory(), cipherSuite), false); + + Call call = client.newCall(new Request.Builder() + .url(server.url("/")) + .build()); + Response response = call.execute(); + assertEquals("abc", response.body().string()); + assertNull(response.handshake().peerPrincipal()); + assertEquals(Collections.emptyList(), response.handshake().peerCertificates()); + assertEquals(cipherSuite, response.handshake().cipherSuite()); + } + + @Test public void tlsHostnameVerificationFailure() throws Exception { + server.enqueue(new MockResponse()); + + HeldCertificate serverCertificate = new HeldCertificate.Builder() + .commonName("localhost") // Unusued for hostname verification. + .addSubjectAlternativeName("wronghostname") + .build(); + + HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder() + .heldCertificate(serverCertificate) + .build(); + + HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder() + .addTrustedCertificate(serverCertificate.certificate()) + .build(); + + client = client.newBuilder() + .sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager()) + .build(); + server.useHttps(serverCertificates.sslSocketFactory(), false); + + executeSynchronously("/") + .assertFailureMatches("(?s)Hostname localhost not verified.*"); + } + + @Test public void tlsHostnameVerificationFailureNoPeerCertificates() throws Exception { + server.enqueue(new MockResponse()); + + // The _anon_ cipher suites don't require server certificates. + CipherSuite cipherSuite = TLS_DH_anon_WITH_AES_128_GCM_SHA256; + + HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder() + .build(); + client = client.newBuilder() + .sslSocketFactory( + socketFactoryWithCipherSuite(clientCertificates.sslSocketFactory(), cipherSuite), + clientCertificates.trustManager()) + .connectionSpecs(Arrays.asList(new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) + .cipherSuites(cipherSuite) + .build())) + .build(); + + HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder() + .build(); + server.useHttps(socketFactoryWithCipherSuite( + serverCertificates.sslSocketFactory(), cipherSuite), false); + + executeSynchronously("/") + .assertFailure("Hostname localhost not verified (no certificates)"); + } + @Test public void cleartextCallsFailWhenCleartextIsDisabled() throws Exception { // Configure the client with only TLS configurations. No cleartext! client = client.newBuilder() @@ -1580,7 +1672,6 @@ public final class CallTest { .assertBody("A"); // Attempt conditional cache validation and a DNS miss. - client.connectionPool().evictAll(); client = client.newBuilder() .dns(new FakeDns()) .build(); @@ -2062,15 +2153,13 @@ public final class CallTest { server.enqueue(new MockResponse()); final CountDownLatch latch = new CountDownLatch(1); client = client.newBuilder() - .addNetworkInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - try { - latch.await(); - } catch (InterruptedException e) { - throw new AssertionError(e); - } - return chain.proceed(chain.request()); + .addNetworkInterceptor(chain -> { + try { + latch.await(); + } catch (InterruptedException e) { + throw new AssertionError(e); } + return chain.proceed(chain.request()); }) .build(); @@ -2093,16 +2182,44 @@ public final class CallTest { callback.await(server.url("/")).assertFailure("Canceled", "Socket closed"); } + @Test + public void cancelWhileRequestHeadersAreSent() throws Exception { + server.enqueue(new MockResponse().setBody("A")); + + EventListener listener = + new EventListener() { + @Override + public void requestHeadersStart(Call call) { + try { + // Cancel call from another thread to avoid reentrance. + cancelLater(call, 0).join(); + } catch (InterruptedException e) { + throw new AssertionError(); + } + } + }; + client = client.newBuilder().eventListener(listener).build(); + + Call call = client.newCall(new Request.Builder().url(server.url("/a")).build()); + try { + call.execute(); + fail(); + } catch (IOException expected) { + } + } + + @Test + public void cancelWhileRequestHeadersAreSent_HTTP_2() throws Exception { + enableProtocol(Protocol.HTTP_2); + cancelWhileRequestHeadersAreSent(); + } + @Test public void cancelBeforeBodyIsRead() throws Exception { server.enqueue(new MockResponse().setBody("def").throttleBody(1, 750, TimeUnit.MILLISECONDS)); final Call call = client.newCall(new Request.Builder().url(server.url("/a")).build()); ExecutorService executor = Executors.newSingleThreadExecutor(); - Future<Response> result = executor.submit(new Callable<Response>() { - @Override public Response call() throws Exception { - return call.execute(); - } - }); + Future<Response> result = executor.submit(call::execute); Thread.sleep(100); // wait for it to go in flight. @@ -2266,12 +2383,11 @@ public final class CallTest { @Test public void cancelWithInterceptor() throws Exception { client = client.newBuilder() - .addInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - chain.proceed(chain.request()); - throw new AssertionError(); // We expect an exception. - } - }).build(); + .addInterceptor(chain -> { + chain.proceed(chain.request()); + throw new AssertionError(); // We expect an exception. + }) + .build(); Call call = client.newCall(new Request.Builder().url(server.url("/a")).build()); call.cancel(); @@ -2890,19 +3006,17 @@ public final class CallTest { handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager()) .proxy(server.toProxyAddress()) .hostnameVerifier(new RecordingHostnameVerifier()) - .proxyAuthenticator(new Authenticator() { - @Override public Request authenticate(Route route, Response response) { - assertEquals("CONNECT", response.request().method()); - assertEquals(HttpURLConnection.HTTP_PROXY_AUTH, response.code()); - assertEquals("android.com", response.request().url().host()); - - List<Challenge> challenges = response.challenges(); - assertEquals("OkHttp-Preemptive", challenges.get(0).scheme()); - - return response.request().newBuilder() - .header("Proxy-Authorization", credential) - .build(); - } + .proxyAuthenticator((route, response) -> { + assertEquals("CONNECT", response.request().method()); + assertEquals(HttpURLConnection.HTTP_PROXY_AUTH, response.code()); + assertEquals("android.com", response.request().url().host()); + + List<Challenge> challenges = response.challenges(); + assertEquals("OkHttp-Preemptive", challenges.get(0).scheme()); + + return response.request().newBuilder() + .header("Proxy-Authorization", credential) + .build(); }) .build(); @@ -2942,14 +3056,12 @@ public final class CallTest { handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager()) .proxy(server.toProxyAddress()) .hostnameVerifier(new RecordingHostnameVerifier()) - .proxyAuthenticator(new Authenticator() { - @Override public Request authenticate(Route route, Response response) { - List<Challenge> challenges = response.challenges(); - challengeSchemes.add(challenges.get(0).scheme()); - return response.request().newBuilder() - .header("Proxy-Authorization", credential) - .build(); - } + .proxyAuthenticator((route, response) -> { + List<Challenge> challenges = response.challenges(); + challengeSchemes.add(challenges.get(0).scheme()); + return response.request().newBuilder() + .header("Proxy-Authorization", credential) + .build(); }) .build(); @@ -2975,11 +3087,9 @@ public final class CallTest { // Capture the protocol as it is observed by the interceptor. final AtomicReference<Protocol> protocolRef = new AtomicReference<>(); - Interceptor interceptor = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - protocolRef.set(chain.connection().protocol()); - return chain.proceed(chain.request()); - } + Interceptor interceptor = chain -> { + protocolRef.set(chain.connection().protocol()); + return chain.proceed(chain.request()); }; client = client.newBuilder() .addNetworkInterceptor(interceptor) @@ -3276,13 +3386,8 @@ public final class CallTest { server.enqueue(new MockResponse() .setResponseCode(401)); - client.connectionPool().evictAll(); client = client.newBuilder() - .authenticator(new Authenticator() { - @Override public Request authenticate(Route route, Response response) throws IOException { - throw new IOException("IOException!"); - } - }) + .authenticator((route, response) -> { throw new IOException("IOException!"); }) .build(); Request request = new Request.Builder() @@ -3299,13 +3404,8 @@ public final class CallTest { server.enqueue(new MockResponse() .setResponseCode(407)); - client.connectionPool().evictAll(); client = client.newBuilder() - .proxyAuthenticator(new Authenticator() { - @Override public Request authenticate(Route route, Response response) throws IOException { - throw new IOException("IOException!"); - } - }) + .proxyAuthenticator((route, response) -> { throw new IOException("IOException!"); }) .build(); Request request = new Request.Builder() @@ -3375,12 +3475,70 @@ public final class CallTest { .post(body) .build(); + client = client.newBuilder() + .dns(new DoubleInetAddressDns()) + .build(); + executeSynchronously(request) .assertFailure(FileNotFoundException.class); assertEquals(1L, called.get()); } + @Test public void clientReadsHeadersDataTrailersHttp1ChunkedTransferEncoding() throws Exception { + MockResponse mockResponse = new MockResponse() + .clearHeaders() + .addHeader("h1", "v1") + .addHeader("h2", "v2") + .setChunkedBody("HelloBonjour", 1024) + .setTrailers(Headers.of("trailers", "boom")); + server.enqueue(mockResponse); + + Call call = client.newCall(new Request.Builder() + .url(server.url("/")) + .build()); + + Response response = call.execute(); + BufferedSource source = response.body().source(); + + assertEquals("v1", response.header("h1")); + assertEquals("v2", response.header("h2")); + + assertEquals("Hello", source.readUtf8(5)); + assertEquals("Bonjour", source.readUtf8(7)); + + assertTrue(source.exhausted()); + assertEquals(Headers.of("trailers", "boom"), response.trailers()); + } + + @Test public void clientReadsHeadersDataTrailersHttp2() throws IOException { + MockResponse mockResponse = new MockResponse() + .clearHeaders() + .addHeader("h1", "v1") + .addHeader("h2", "v2") + .setBody("HelloBonjour") + .setTrailers(Headers.of("trailers", "boom")); + server.enqueue(mockResponse); + enableProtocol(Protocol.HTTP_2); + + Call call = client.newCall(new Request.Builder() + .url(server.url("/")) + .build()); + + try (Response response = call.execute()) { + BufferedSource source = response.body().source(); + + assertEquals("v1", response.header("h1")); + assertEquals("v2", response.header("h2")); + + assertEquals("Hello", source.readUtf8(5)); + assertEquals("Bonjour", source.readUtf8(7)); + + assertTrue(source.exhausted()); + assertEquals(Headers.of("trailers", "boom"), response.trailers()); + } + } + private void makeFailingCall() { RequestBody requestBody = new RequestBody() { @Override public MediaType contentType() { @@ -3459,8 +3617,8 @@ public final class CallTest { return result; } - private void cancelLater(final Call call, final long delay) { - new Thread("canceler") { + private Thread cancelLater(final Call call, final long delay) { + Thread thread = new Thread("canceler") { @Override public void run() { try { Thread.sleep(delay); @@ -3469,7 +3627,19 @@ public final class CallTest { } call.cancel(); } - }.start(); + }; + thread.start(); + return thread; + } + + private SSLSocketFactory socketFactoryWithCipherSuite( + final SSLSocketFactory sslSocketFactory, final CipherSuite cipherSuite) { + return new DelegatingSSLSocketFactory(sslSocketFactory) { + @Override protected SSLSocket configureSocket(SSLSocket sslSocket) throws IOException { + sslSocket.setEnabledCipherSuites(new String[] { cipherSuite.javaName() }); + return super.configureSocket(sslSocket); + } + }; } private static class RecordingSSLSocketFactory extends DelegatingSSLSocketFactory { diff --git a/okhttp-tests/src/test/java/okhttp3/CertificatePinnerTest.java b/okhttp-tests/src/test/java/okhttp3/CertificatePinnerTest.java index 28e2af84230cbc3c4746e34893cecb8ed956c358..a0e03cdcc6cadd19a45c5dad46c715c5955c64d8 100644 --- a/okhttp-tests/src/test/java/okhttp3/CertificatePinnerTest.java +++ b/okhttp-tests/src/test/java/okhttp3/CertificatePinnerTest.java @@ -24,8 +24,7 @@ import okhttp3.tls.HeldCertificate; import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.fail; public final class CertificatePinnerTest { @@ -87,9 +86,9 @@ public final class CertificatePinnerTest { .build(); String keypairBCertificate2Pin = CertificatePinner.pin(heldCertificateB2.certificate()); - assertTrue(certA1Sha256Pin.equals(keypairACertificate2Pin)); - assertTrue(certB1Sha256Pin.equals(keypairBCertificate2Pin)); - assertFalse(certA1Sha256Pin.equals(certB1Sha256Pin)); + assertEquals(certA1Sha256Pin, keypairACertificate2Pin); + assertEquals(certB1Sha256Pin, keypairBCertificate2Pin); + assertNotEquals(certA1Sha256Pin, certB1Sha256Pin); } @Test public void successfulCheck() throws Exception { diff --git a/okhttp-tests/src/test/java/okhttp3/ConnectionCoalescingTest.java b/okhttp-tests/src/test/java/okhttp3/ConnectionCoalescingTest.java index be741a0b53a02637868cabbe6503d4681579f62a..c792d75ba09b9837ef57be9ed6e233dd4424929b 100644 --- a/okhttp-tests/src/test/java/okhttp3/ConnectionCoalescingTest.java +++ b/okhttp-tests/src/test/java/okhttp3/ConnectionCoalescingTest.java @@ -25,11 +25,10 @@ import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSession; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; -import okhttp3.tls.HeldCertificate; import okhttp3.tls.HandshakeCertificates; +import okhttp3.tls.HeldCertificate; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -71,7 +70,7 @@ public final class ConnectionCoalescingTest { dns.set("san.com", serverIps); dns.set("nonsan.com", serverIps); dns.set("www.wildcard.com", serverIps); - dns.set("differentdns.com", Collections.<InetAddress>emptyList()); + dns.set("differentdns.com", Collections.emptyList()); HandshakeCertificates handshakeCertificates = new HandshakeCertificates.Builder() .addTrustedCertificate(rootCa.certificate()) @@ -127,13 +126,11 @@ public final class ConnectionCoalescingTest { server.enqueue(new MockResponse().setResponseCode(200)); server.enqueue(new MockResponse().setResponseCode(200)); - final AtomicReference<Connection> connection = new AtomicReference<>(); + AtomicReference<Connection> connection = new AtomicReference<>(); client = client.newBuilder() - .addNetworkInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - connection.set(chain.connection()); - return chain.proceed(chain.request()); - } + .addNetworkInterceptor(chain -> { + connection.set(chain.connection()); + return chain.proceed(chain.request()); }) .build(); dns.set("san.com", Dns.SYSTEM.lookup(server.getHostName()).subList(0, 1)); @@ -222,11 +219,7 @@ public final class ConnectionCoalescingTest { * verification is a black box. */ @Test public void skipsWhenHostnameVerifierUsed() throws Exception { - HostnameVerifier verifier = new HostnameVerifier() { - @Override public boolean verify(String s, SSLSession sslSession) { - return true; - } - }; + HostnameVerifier verifier = (name, session) -> true; client = client.newBuilder().hostnameVerifier(verifier).build(); server.enqueue(new MockResponse().setResponseCode(200)); @@ -249,7 +242,7 @@ public final class ConnectionCoalescingTest { server.enqueue(new MockResponse().setResponseCode(200)); server.enqueue(new MockResponse().setResponseCode(200)); - final AtomicInteger connectCount = new AtomicInteger(); + AtomicInteger connectCount = new AtomicInteger(); EventListener listener = new EventListener() { @Override public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) { @@ -288,11 +281,9 @@ public final class ConnectionCoalescingTest { /** Network interceptors check for changes to target. */ @Test public void worksWithNetworkInterceptors() throws Exception { - client = client.newBuilder().addNetworkInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - return chain.proceed(chain.request()); - } - }).build(); + client = client.newBuilder() + .addNetworkInterceptor(chain -> chain.proceed(chain.request())) + .build(); server.enqueue(new MockResponse().setResponseCode(200)); server.enqueue(new MockResponse().setResponseCode(200)); diff --git a/okhttp-tests/src/test/java/okhttp3/ConnectionPoolTest.java b/okhttp-tests/src/test/java/okhttp3/ConnectionPoolTest.java index d2c778ce655bee908b6e27be1f99811d113545dc..b95f8521a5f0954ffb9712454d8a3eeddc1a9a6e 100644 --- a/okhttp-tests/src/test/java/okhttp3/ConnectionPoolTest.java +++ b/okhttp-tests/src/test/java/okhttp3/ConnectionPoolTest.java @@ -193,9 +193,8 @@ public final class ConnectionPoolTest { private Address newAddress(String name) { return new Address(name, 1, Dns.SYSTEM, SocketFactory.getDefault(), null, null, null, - new RecordingOkAuthenticator("password", null), null, Collections.<Protocol>emptyList(), - Collections.<ConnectionSpec>emptyList(), - ProxySelector.getDefault()); + new RecordingOkAuthenticator("password", null), null, Collections.emptyList(), + Collections.emptyList(), ProxySelector.getDefault()); } private Route newRoute(Address address) { diff --git a/okhttp-tests/src/test/java/okhttp3/ConnectionReuseTest.java b/okhttp-tests/src/test/java/okhttp3/ConnectionReuseTest.java index f5c8e75c8566c82eed844a9fa7d5b0f9e638b5ce..f8c4991120c7679fa9f52170eabc571e30053503 100644 --- a/okhttp-tests/src/test/java/okhttp3/ConnectionReuseTest.java +++ b/okhttp-tests/src/test/java/okhttp3/ConnectionReuseTest.java @@ -15,7 +15,6 @@ */ package okhttp3; -import java.io.IOException; import java.util.Arrays; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLException; @@ -301,14 +300,18 @@ public final class ConnectionReuseTest { * https://github.com/square/okhttp/issues/2409 */ @Test public void connectionsAreNotReusedIfNetworkInterceptorInterferes() throws Exception { - client = client.newBuilder().addNetworkInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - Response response = chain.proceed(chain.request()); - return response.newBuilder() - .body(ResponseBody.create(null, "unrelated response body!")) - .build(); - } - }).build(); + client = client.newBuilder() + // Since this test knowingly leaks a connection, avoid using the default shared connection + // pool, which should remain clean for subsequent tests. + .connectionPool(new ConnectionPool()) + .addNetworkInterceptor(chain -> { + Response response = chain.proceed(chain.request()); + return response + .newBuilder() + .body(ResponseBody.create(null, "unrelated response body!")) + .build(); + }) + .build(); server.enqueue(new MockResponse() .setResponseCode(301) diff --git a/okhttp-tests/src/test/java/okhttp3/ConnectionSpecTest.java b/okhttp-tests/src/test/java/okhttp3/ConnectionSpecTest.java index c74a6b314c371f5b72320ae4851606ca49652116..13b2824f060082565b4ca598f2ddcf6f8e26e53c 100644 --- a/okhttp-tests/src/test/java/okhttp3/ConnectionSpecTest.java +++ b/okhttp-tests/src/test/java/okhttp3/ConnectionSpecTest.java @@ -297,6 +297,7 @@ public final class ConnectionSpecTest { + "supportsTlsExtensions=true)", connectionSpec.toString()); } + @SafeVarargs private static <T> Set<T> set(T... values) { return new LinkedHashSet<>(Arrays.asList(values)); } diff --git a/okhttp-tests/src/test/java/okhttp3/CookieTest.java b/okhttp-tests/src/test/java/okhttp3/CookieTest.java index b38dd3dd9a1c1b0a04debbf97d697651f3bda4e9..3304fa588a44f5323aefcc9ad678c5c49ae0b609 100644 --- a/okhttp-tests/src/test/java/okhttp3/CookieTest.java +++ b/okhttp-tests/src/test/java/okhttp3/CookieTest.java @@ -27,6 +27,7 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -79,20 +80,20 @@ public final class CookieTest { } @Test public void invalidCharacters() throws Exception { - assertEquals(null, Cookie.parse(url, "a\u0000b=cd")); - assertEquals(null, Cookie.parse(url, "ab=c\u0000d")); - assertEquals(null, Cookie.parse(url, "a\u0001b=cd")); - assertEquals(null, Cookie.parse(url, "ab=c\u0001d")); - assertEquals(null, Cookie.parse(url, "a\u0009b=cd")); - assertEquals(null, Cookie.parse(url, "ab=c\u0009d")); - assertEquals(null, Cookie.parse(url, "a\u001fb=cd")); - assertEquals(null, Cookie.parse(url, "ab=c\u001fd")); - assertEquals(null, Cookie.parse(url, "a\u007fb=cd")); - assertEquals(null, Cookie.parse(url, "ab=c\u007fd")); - assertEquals(null, Cookie.parse(url, "a\u0080b=cd")); - assertEquals(null, Cookie.parse(url, "ab=c\u0080d")); - assertEquals(null, Cookie.parse(url, "a\u00ffb=cd")); - assertEquals(null, Cookie.parse(url, "ab=c\u00ffd")); + assertNull(Cookie.parse(url, "a\u0000b=cd")); + assertNull(Cookie.parse(url, "ab=c\u0000d")); + assertNull(Cookie.parse(url, "a\u0001b=cd")); + assertNull(Cookie.parse(url, "ab=c\u0001d")); + assertNull(Cookie.parse(url, "a\u0009b=cd")); + assertNull(Cookie.parse(url, "ab=c\u0009d")); + assertNull(Cookie.parse(url, "a\u001fb=cd")); + assertNull(Cookie.parse(url, "ab=c\u001fd")); + assertNull(Cookie.parse(url, "a\u007fb=cd")); + assertNull(Cookie.parse(url, "ab=c\u007fd")); + assertNull(Cookie.parse(url, "a\u0080b=cd")); + assertNull(Cookie.parse(url, "ab=c\u0080d")); + assertNull(Cookie.parse(url, "a\u00ffb=cd")); + assertNull(Cookie.parse(url, "ab=c\u00ffd")); } @Test public void maxAge() throws Exception { @@ -523,7 +524,7 @@ public final class CookieTest { .hostOnlyDomain("example.com") .secure() .build(); - assertEquals(true, cookie.secure()); + assertTrue(cookie.secure()); } @Test public void builderHttpOnly() throws Exception { @@ -533,7 +534,7 @@ public final class CookieTest { .hostOnlyDomain("example.com") .httpOnly() .build(); - assertEquals(true, cookie.httpOnly()); + assertTrue(cookie.httpOnly()); } @Test public void builderIpv6() throws Exception { @@ -563,11 +564,11 @@ public final class CookieTest { assertEquals(cookieA.hashCode(), cookieB.hashCode()); assertEquals(cookieA, cookieB); } else { - assertFalse(cookieA.hashCode() == cookieB.hashCode()); - assertFalse(cookieA.equals(cookieB)); + assertNotEquals(cookieA.hashCode(), cookieB.hashCode()); + assertNotEquals(cookieA, cookieB); } } - assertFalse(cookieA.equals(null)); + assertNotEquals(null, cookieA); } } diff --git a/okhttp-tests/src/test/java/okhttp3/CookiesTest.java b/okhttp-tests/src/test/java/okhttp3/CookiesTest.java index 17489b045de2180b277376bae31848a242a15deb..add061e3fe9effca181528534170a4e59441e4a4 100644 --- a/okhttp-tests/src/test/java/okhttp3/CookiesTest.java +++ b/okhttp-tests/src/test/java/okhttp3/CookiesTest.java @@ -36,6 +36,7 @@ import org.junit.Test; import static java.net.CookiePolicy.ACCEPT_ORIGINAL_SERVER; import static okhttp3.TestUtil.defaultClient; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -66,12 +67,12 @@ public class CookiesTest { HttpCookie cookie = cookies.get(0); assertEquals("a", cookie.getName()); assertEquals("android", cookie.getValue()); - assertEquals(null, cookie.getComment()); - assertEquals(null, cookie.getCommentURL()); - assertEquals(false, cookie.getDiscard()); + assertNull(cookie.getComment()); + assertNull(cookie.getCommentURL()); + assertFalse(cookie.getDiscard()); assertTrue(cookie.getMaxAge() > 100000000000L); assertEquals("/path", cookie.getPath()); - assertEquals(true, cookie.getSecure()); + assertTrue(cookie.getSecure()); assertEquals(0, cookie.getVersion()); } @@ -98,11 +99,11 @@ public class CookiesTest { HttpCookie cookie = cookies.get(0); assertEquals("a", cookie.getName()); assertEquals("android", cookie.getValue()); - assertEquals(null, cookie.getCommentURL()); - assertEquals(false, cookie.getDiscard()); + assertNull(cookie.getCommentURL()); + assertFalse(cookie.getDiscard()); assertEquals(60.0, cookie.getMaxAge(), 1.0); // Converting to a fixed date can cause rounding! assertEquals("/path", cookie.getPath()); - assertEquals(true, cookie.getSecure()); + assertTrue(cookie.getSecure()); } @Test public void testQuotedAttributeValues() throws Exception { @@ -133,7 +134,7 @@ public class CookiesTest { assertEquals("android", cookie.getValue()); assertEquals(60.0, cookie.getMaxAge(), 1.0); // Converting to a fixed date can cause rounding! assertEquals("/path", cookie.getPath()); - assertEquals(true, cookie.getSecure()); + assertTrue(cookie.getSecure()); } @Test public void testSendingCookiesFromStore() throws Exception { diff --git a/okhttp-tests/src/test/java/okhttp3/DelegatingSSLSocket.java b/okhttp-tests/src/test/java/okhttp3/DelegatingSSLSocket.java index fc863c202205e25c98d31ccacd5f204d5c29315d..e6c931b6ad01bbe07dc5dec63a9c77c382dd35a8 100644 --- a/okhttp-tests/src/test/java/okhttp3/DelegatingSSLSocket.java +++ b/okhttp-tests/src/test/java/okhttp3/DelegatingSSLSocket.java @@ -320,6 +320,7 @@ public abstract class DelegatingSSLSocket extends SSLSocket { } } + @SuppressWarnings("unchecked") // Using reflection to delegate. public <T> T getOption(SocketOption<T> name) throws IOException { try { return (T) SSLSocket.class.getMethod("getOption", SocketOption.class).invoke(delegate, name); @@ -328,6 +329,7 @@ public abstract class DelegatingSSLSocket extends SSLSocket { } } + @SuppressWarnings("unchecked") // Using reflection to delegate. public Set<SocketOption<?>> supportedOptions() { try { return (Set<SocketOption<?>>) SSLSocket.class.getMethod("supportedOptions").invoke(delegate); diff --git a/okhttp-tests/src/test/java/okhttp3/DispatcherTest.java b/okhttp-tests/src/test/java/okhttp3/DispatcherTest.java index 80cdb78cfdb231c65f46cf9975f0fcbc2ec33c2f..fd7d499412b1652b3eee238ec0c230bd9d72658c 100644 --- a/okhttp-tests/src/test/java/okhttp3/DispatcherTest.java +++ b/okhttp-tests/src/test/java/okhttp3/DispatcherTest.java @@ -138,6 +138,18 @@ public final class DispatcherTest { executor.assertJobs("http://a/1"); } + @Test public void enqueuedCallsStillRespectMaxCallsPerHost() throws Exception { + dispatcher.setMaxRequests(1); + dispatcher.setMaxRequestsPerHost(1); + client.newCall(newRequest("http://a/1")).enqueue(callback); + client.newCall(newRequest("http://b/1")).enqueue(callback); + client.newCall(newRequest("http://b/2")).enqueue(callback); + client.newCall(newRequest("http://b/3")).enqueue(callback); + dispatcher.setMaxRequests(3); + executor.finishJob("http://a/1"); + executor.assertJobs("http://b/1"); + } + @Test public void cancelingRunningJobTakesNoEffectUntilJobFinishes() throws Exception { dispatcher.setMaxRequests(1); Call c1 = client.newCall(newRequest("http://a/1", "tag1")); @@ -169,21 +181,18 @@ public final class DispatcherTest { } @Test public void synchronousCallAccessors() throws Exception { - final CountDownLatch ready = new CountDownLatch(2); - final CountDownLatch waiting = new CountDownLatch(1); + CountDownLatch ready = new CountDownLatch(2); + CountDownLatch waiting = new CountDownLatch(1); client = client.newBuilder() - .addInterceptor( - new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - try { - ready.countDown(); - waiting.await(); - } catch (InterruptedException e) { - throw new AssertionError(); - } - throw new IOException(); - } - }) + .addInterceptor(chain -> { + try { + ready.countDown(); + waiting.await(); + } catch (InterruptedException e) { + throw new AssertionError(); + } + throw new IOException(); + }) .build(); Call a1 = client.newCall(newRequest("http://a/1")); @@ -231,31 +240,25 @@ public final class DispatcherTest { } @Test public void idleCallbackInvokedWhenIdle() throws Exception { - final AtomicBoolean idle = new AtomicBoolean(); - dispatcher.setIdleCallback(new Runnable() { - @Override public void run() { - idle.set(true); - } - }); + AtomicBoolean idle = new AtomicBoolean(); + dispatcher.setIdleCallback(() -> idle.set(true)); client.newCall(newRequest("http://a/1")).enqueue(callback); client.newCall(newRequest("http://a/2")).enqueue(callback); executor.finishJob("http://a/1"); assertFalse(idle.get()); - final CountDownLatch ready = new CountDownLatch(1); - final CountDownLatch proceed = new CountDownLatch(1); + CountDownLatch ready = new CountDownLatch(1); + CountDownLatch proceed = new CountDownLatch(1); client = client.newBuilder() - .addInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - ready.countDown(); - try { - proceed.await(5, SECONDS); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - return chain.proceed(chain.request()); + .addInterceptor(chain -> { + ready.countDown(); + try { + proceed.await(5, SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); } + return chain.proceed(chain.request()); }) .build(); @@ -317,7 +320,8 @@ public final class DispatcherTest { listener.recordedEventTypes()); } - private <T> Set<T> set(T... values) { + @SafeVarargs + private final <T> Set<T> set(T... values) { return set(Arrays.asList(values)); } @@ -325,16 +329,14 @@ public final class DispatcherTest { return new LinkedHashSet<>(list); } - private Thread makeSynchronousCall(final Call call) { - Thread thread = new Thread() { - @Override public void run() { - try { - call.execute(); - throw new AssertionError(); - } catch (IOException expected) { - } + private Thread makeSynchronousCall(Call call) { + Thread thread = new Thread(() -> { + try { + call.execute(); + throw new AssertionError(); + } catch (IOException expected) { } - }; + }); thread.start(); return thread; } diff --git a/okhttp-tests/src/test/java/okhttp3/DuplexTest.java b/okhttp-tests/src/test/java/okhttp3/DuplexTest.java new file mode 100644 index 0000000000000000000000000000000000000000..71284133d0e876556f41a865c23d28991f4cf806 --- /dev/null +++ b/okhttp-tests/src/test/java/okhttp3/DuplexTest.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2018 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package okhttp3; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; +import okhttp3.internal.duplex.AsyncRequestBody; +import okhttp3.internal.duplex.MwsDuplexAccess; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.internal.duplex.MockDuplexResponseBody; +import okhttp3.tls.HandshakeCertificates; +import okio.BufferedSink; +import okio.BufferedSource; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; + +import static junit.framework.TestCase.assertTrue; +import static okhttp3.TestUtil.defaultClient; +import static okhttp3.tls.internal.TlsUtil.localhost; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public final class DuplexTest { + @Rule public final TestRule timeout = new Timeout(30_000, TimeUnit.MILLISECONDS); + @Rule public final MockWebServer server = new MockWebServer(); + + private HandshakeCertificates handshakeCertificates = localhost(); + private OkHttpClient client = defaultClient(); + + @Test public void trueDuplexClientWritesFirst() throws Exception { + enableProtocol(Protocol.HTTP_2); + MockDuplexResponseBody mockDuplexResponseBody = enqueueResponseWithBody( + new MockResponse() + .clearHeaders(), + new MockDuplexResponseBody() + .receiveRequest("request A\n") + .sendResponse("response B\n") + .receiveRequest("request C\n") + .sendResponse("response D\n") + .receiveRequest("request E\n") + .sendResponse("response F\n") + .exhaustRequest()); + + Call call = client.newCall(new Request.Builder() + .url(server.url("/")) + .post(new AsyncRequestBody()) + .build()); + + try (Response response = call.execute()) { + BufferedSink requestBody = ((AsyncRequestBody) call.request().body()).takeSink(); + requestBody.writeUtf8("request A\n"); + requestBody.flush(); + + BufferedSource responseBody = response.body().source(); + assertEquals("response B", responseBody.readUtf8Line()); + + requestBody.writeUtf8("request C\n"); + requestBody.flush(); + assertEquals("response D", responseBody.readUtf8Line()); + + requestBody.writeUtf8("request E\n"); + requestBody.flush(); + assertEquals("response F", responseBody.readUtf8Line()); + + requestBody.close(); + assertNull(responseBody.readUtf8Line()); + } + + mockDuplexResponseBody.awaitSuccess(); + } + + @Test public void trueDuplexServerWritesFirst() throws Exception { + enableProtocol(Protocol.HTTP_2); + MockDuplexResponseBody mockDuplexResponseBody = enqueueResponseWithBody( + new MockResponse() + .clearHeaders(), + new MockDuplexResponseBody() + .sendResponse("response A\n") + .receiveRequest("request B\n") + .sendResponse("response C\n") + .receiveRequest("request D\n") + .sendResponse("response E\n") + .receiveRequest("request F\n")); + + Call call = client.newCall(new Request.Builder() + .url(server.url("/")) + .post(new AsyncRequestBody()) + .build()); + + try (Response response = call.execute()) { + BufferedSink requestBody = ((AsyncRequestBody) call.request().body()).takeSink(); + BufferedSource responseBody = response.body().source(); + + assertEquals("response A", responseBody.readUtf8Line()); + requestBody.writeUtf8("request B\n"); + requestBody.flush(); + + assertEquals("response C", responseBody.readUtf8Line()); + requestBody.writeUtf8("request D\n"); + requestBody.flush(); + + assertEquals("response E", responseBody.readUtf8Line()); + requestBody.writeUtf8("request F\n"); + requestBody.flush(); + + assertNull(responseBody.readUtf8Line()); + requestBody.close(); + } + + mockDuplexResponseBody.awaitSuccess(); + } + + @Test public void clientReadsHeadersDataTrailers() throws Exception { + enableProtocol(Protocol.HTTP_2); + MockDuplexResponseBody mockDuplexResponseBody = enqueueResponseWithBody( + new MockResponse() + .clearHeaders() + .addHeader("h1", "v1") + .addHeader("h2", "v2") + .setTrailers(Headers.of("trailers", "boom")), + new MockDuplexResponseBody() + .sendResponse("ok") + .exhaustResponse()); + + Call call = client.newCall(new Request.Builder() + .url(server.url("/")) + .build()); + + try (Response response = call.execute()) { + assertEquals(Headers.of("h1", "v1", "h2", "v2"), response.headers()); + + BufferedSource responseBody = response.body().source(); + assertEquals("ok", responseBody.readUtf8(2)); + assertTrue(responseBody.exhausted()); + assertEquals(Headers.of("trailers", "boom"), response.trailers()); + } + + mockDuplexResponseBody.awaitSuccess(); + } + + @Test public void serverReadsHeadersData() throws Exception { + enableProtocol(Protocol.HTTP_2); + MockDuplexResponseBody mockDuplexResponseBody = enqueueResponseWithBody( + new MockResponse() + .clearHeaders() + .addHeader("h1", "v1") + .addHeader("h2", "v2"), + new MockDuplexResponseBody() + .exhaustResponse() + .receiveRequest("hey\n") + .receiveRequest("whats going on\n") + .exhaustRequest()); + + Request request = new Request.Builder() + .url(server.url("/")) + .method("POST", new AsyncRequestBody()) + .build(); + Call call = client.newCall(request); + + try (Response response = call.execute()) { + BufferedSink sink = ((AsyncRequestBody) request.body()).takeSink(); + sink.writeUtf8("hey\n"); + sink.writeUtf8("whats going on\n"); + sink.close(); + } + + mockDuplexResponseBody.awaitSuccess(); + } + + // TODO(oldergod) write tests for headers discarded with 100 Continue + + private MockDuplexResponseBody enqueueResponseWithBody( + MockResponse response, MockDuplexResponseBody body) { + MwsDuplexAccess.instance.setBody(response, body); + server.enqueue(response); + return body; + } + + /** + * Tests that use this will fail unless boot classpath is set. Ex. {@code + * -Xbootclasspath/p:/tmp/alpn-boot-8.0.0.v20140317} + */ + private void enableProtocol(Protocol protocol) { + enableTls(); + client = client.newBuilder() + .protocols(Arrays.asList(protocol, Protocol.HTTP_1_1)) + .build(); + server.setProtocols(client.protocols()); + } + + private void enableTls() { + client = client.newBuilder() + .sslSocketFactory( + handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager()) + .hostnameVerifier(new RecordingHostnameVerifier()) + .build(); + server.useHttps(handshakeCertificates.sslSocketFactory(), false); + } +} diff --git a/okhttp-tests/src/test/java/okhttp3/EventListenerTest.java b/okhttp-tests/src/test/java/okhttp3/EventListenerTest.java index 14758e244ce0d8eed93890fb7b9f71dea7ca5feb..fd5f2c2813b03bb5ef90376f09eee86a2e81841b 100644 --- a/okhttp-tests/src/test/java/okhttp3/EventListenerTest.java +++ b/okhttp-tests/src/test/java/okhttp3/EventListenerTest.java @@ -16,6 +16,7 @@ package okhttp3; import java.io.IOException; +import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Proxy; @@ -475,11 +476,7 @@ public final class EventListenerTest { } @Test public void emptyDnsLookup() { - Dns emptyDns = new Dns() { - @Override public List<InetAddress> lookup(String hostname) { - return Collections.emptyList(); - } - }; + Dns emptyDns = hostname -> Collections.emptyList(); client = client.newBuilder() .dns(emptyDns) @@ -927,6 +924,25 @@ public final class EventListenerTest { assertEquals(expectedEvents, listener.recordedEventTypes()); } + @Ignore("CallEnd not emitted") + @Test public void emptyResponseBodyConnectionClose() throws IOException { + server.enqueue(new MockResponse() + .addHeader("Connection", "close") + .setBody("")); + + Call call = client.newCall(new Request.Builder() + .url(server.url("/")) + .build()); + Response response = call.execute(); + response.body().close(); + + List<String> expectedEvents = Arrays.asList("CallStart", "DnsStart", "DnsEnd", + "ConnectStart", "ConnectEnd", "ConnectionAcquired", "RequestHeadersStart", + "RequestHeadersEnd", "ResponseHeadersStart", "ResponseHeadersEnd", "ResponseBodyStart", + "ResponseBodyEnd", "ConnectionReleased", "CallEnd"); + assertEquals(expectedEvents, listener.recordedEventTypes()); + } + @Ignore("this reports CallFailed not CallEnd") @Test public void responseBodyClosedClosedWithoutReadingAllData() throws IOException { server.enqueue(new MockResponse() @@ -1082,4 +1098,45 @@ public final class EventListenerTest { .build(); server.useHttps(handshakeCertificates.sslSocketFactory(), tunnelProxy); } + + @Test public void redirectUsingSameConnectionEventSequence() throws IOException { + server.enqueue( + new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) + .addHeader("Location: /foo")); + server.enqueue(new MockResponse()); + + Call call = client.newCall(new Request.Builder().url(server.url("/")).build()); + Response response = call.execute(); + + List<String> expectedEvents = Arrays.asList("CallStart", "DnsStart", "DnsEnd", + "ConnectStart", "ConnectEnd", "ConnectionAcquired", "RequestHeadersStart", + "RequestHeadersEnd", "ResponseHeadersStart", "ResponseHeadersEnd", "ResponseBodyStart", + "ResponseBodyEnd", "RequestHeadersStart", "RequestHeadersEnd", "ResponseHeadersStart", + "ResponseHeadersEnd", "ResponseBodyStart", "ResponseBodyEnd", "ConnectionReleased", + "CallEnd"); + assertEquals(expectedEvents, listener.recordedEventTypes()); + } + + @Test + public void redirectUsingNewConnectionEventSequence() throws IOException { + MockWebServer otherServer = new MockWebServer(); + server.enqueue( + new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) + .addHeader("Location: " + otherServer.url("/foo"))); + otherServer.enqueue(new MockResponse()); + + Call call = client.newCall(new Request.Builder().url(server.url("/")).build()); + Response response = call.execute(); + + List<String> expectedEvents = Arrays.asList("CallStart", "DnsStart", "DnsEnd", + "ConnectStart", "ConnectEnd", "ConnectionAcquired", "RequestHeadersStart", + "RequestHeadersEnd", "ResponseHeadersStart", "ResponseHeadersEnd", "ResponseBodyStart", + "ResponseBodyEnd", "ConnectionReleased", "DnsStart", "DnsEnd", "ConnectStart", "ConnectEnd", + "ConnectionAcquired", "RequestHeadersStart", "RequestHeadersEnd", "ResponseHeadersStart", + "ResponseHeadersEnd", "ResponseBodyStart", "ResponseBodyEnd", "ConnectionReleased", + "CallEnd"); + assertEquals(expectedEvents, listener.recordedEventTypes()); + } } diff --git a/okhttp-tests/src/test/java/okhttp3/HeadersTest.java b/okhttp-tests/src/test/java/okhttp3/HeadersTest.java index 494ac90b85632b6f730c4eebaef2b31a6d213ddc..ef30c15d2cc9cfab621e25ec561a927398ced14b 100644 --- a/okhttp-tests/src/test/java/okhttp3/HeadersTest.java +++ b/okhttp-tests/src/test/java/okhttp3/HeadersTest.java @@ -16,6 +16,7 @@ package okhttp3; import java.io.IOException; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.Date; @@ -23,6 +24,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import okhttp3.internal.Internal; +import okhttp3.internal.Util; import okhttp3.internal.http.HttpHeaders; import okhttp3.internal.http2.Header; import okhttp3.internal.http2.Http2Codec; @@ -35,9 +37,8 @@ import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static okhttp3.TestUtil.headerEntries; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public final class HeadersTest { @@ -64,6 +65,7 @@ public final class HeadersTest { .header("Connection", "upgrade") .header("Upgrade", "websocket") .header("Host", "square.com") + .header("TE", "gzip") .build(); List<Header> expected = headerEntries( ":method", "GET", @@ -73,6 +75,19 @@ public final class HeadersTest { assertEquals(expected, Http2Codec.http2HeadersList(request)); } + @Test public void http2HeadersListDontDropTeIfTrailersHttp2() { + Request request = new Request.Builder() + .url("http://square.com/") + .header("TE", "trailers") + .build(); + List<Header> expected = headerEntries( + ":method", "GET", + ":path", "/", + ":scheme", "http", + "te", "trailers"); + assertEquals(expected, Http2Codec.http2HeadersList(request)); + } + @Test public void ofTrims() { Headers headers = Headers.of("\t User-Agent \n", " \r OkHttp "); assertEquals("User-Agent", headers.name(0)); @@ -191,7 +206,7 @@ public final class HeadersTest { @Test public void ofMapThrowsOnNull() { try { - Headers.of(Collections.<String, String>singletonMap("User-Agent", null)); + Headers.of(Collections.singletonMap("User-Agent", null)); fail(); } catch (IllegalArgumentException expected) { } @@ -383,7 +398,7 @@ public final class HeadersTest { .add("Connection", "close") .add("Transfer-Encoding", "chunked") .build(); - assertTrue(headers1.equals(headers2)); + assertEquals(headers1, headers2); assertEquals(headers1.hashCode(), headers2.hashCode()); } @@ -396,8 +411,8 @@ public final class HeadersTest { .add("Connection", "keep-alive") .add("Transfer-Encoding", "chunked") .build(); - assertFalse(headers1.equals(headers2)); - assertFalse(headers1.hashCode() == headers2.hashCode()); + assertNotEquals(headers1, headers2); + assertNotEquals(headers1.hashCode(), headers2.hashCode()); } @Test public void headersToString() { @@ -634,7 +649,7 @@ public final class HeadersTest { .build(); assertEquals(Arrays.asList( new Challenge("Basic", singletonMap("realm", "myrealm")), - new Challenge("Digest", Collections.<String, String>emptyMap())), + new Challenge("Digest", Collections.emptyMap())), HttpHeaders.parseChallenges(headers, "WWW-Authenticate")); } @@ -653,7 +668,7 @@ public final class HeadersTest { .add("WWW-Authenticate", "Digest, Basic ,,realm=\"myrealm\"") .build(); assertEquals(Arrays.asList( - new Challenge("Digest", Collections.<String, String>emptyMap()), + new Challenge("Digest", Collections.emptyMap()), new Challenge("Basic", singletonMap("realm", "myrealm"))), HttpHeaders.parseChallenges(headers, "WWW-Authenticate")); } @@ -663,7 +678,7 @@ public final class HeadersTest { .add("WWW-Authenticate", "Digest,Basic realm=\"myrealm\"") .build(); assertEquals(Arrays.asList( - new Challenge("Digest", Collections.<String, String>emptyMap()), + new Challenge("Digest", Collections.emptyMap()), new Challenge("Basic", singletonMap("realm", "myrealm"))), HttpHeaders.parseChallenges(headers, "WWW-Authenticate")); } @@ -673,7 +688,7 @@ public final class HeadersTest { .add("WWW-Authenticate", "Digest,,,, Basic ,,realm=\"myrealm\"") .build(); assertEquals(Arrays.asList( - new Challenge("Digest", Collections.<String, String>emptyMap()), + new Challenge("Digest", Collections.emptyMap()), new Challenge("Basic", singletonMap("realm", "myrealm"))), HttpHeaders.parseChallenges(headers, "WWW-Authenticate")); } @@ -687,7 +702,7 @@ public final class HeadersTest { expectedAuthParams.put("realm", "myrealm"); expectedAuthParams.put("foo", "bar"); assertEquals(Arrays.asList( - new Challenge("Digest", Collections.<String, String>emptyMap()), + new Challenge("Digest", Collections.emptyMap()), new Challenge("Basic", expectedAuthParams)), HttpHeaders.parseChallenges(headers, "WWW-Authenticate")); } @@ -698,7 +713,7 @@ public final class HeadersTest { .build(); assertEquals(Arrays.asList( - new Challenge("Digest", Collections.<String, String>emptyMap()), + new Challenge("Digest", Collections.emptyMap()), new Challenge("Basic", singletonMap("realm", "my\\\"realm"))), HttpHeaders.parseChallenges(headers, "WWW-Authenticate")); } @@ -709,7 +724,7 @@ public final class HeadersTest { .build(); assertEquals(Arrays.asList( - new Challenge("Digest", Collections.<String, String>emptyMap()), + new Challenge("Digest", Collections.emptyMap()), new Challenge("Basic", singletonMap("realm", "my, realm,"))), HttpHeaders.parseChallenges(headers, "WWW-Authenticate")); } @@ -720,7 +735,7 @@ public final class HeadersTest { .build(); assertEquals(Arrays.asList( - new Challenge("Digest", Collections.<String, String>emptyMap())), + new Challenge("Digest", Collections.emptyMap())), HttpHeaders.parseChallenges(headers, "WWW-Authenticate")); } @@ -730,7 +745,7 @@ public final class HeadersTest { .build(); assertEquals(Arrays.asList( - new Challenge("Digest", Collections.<String, String>emptyMap())), + new Challenge("Digest", Collections.emptyMap())), HttpHeaders.parseChallenges(headers, "WWW-Authenticate")); } @@ -741,7 +756,7 @@ public final class HeadersTest { .build(); assertEquals(Arrays.asList( - new Challenge("Digest", Collections.<String, String>emptyMap())), + new Challenge("Digest", Collections.emptyMap())), HttpHeaders.parseChallenges(headers, "WWW-Authenticate")); } @@ -751,7 +766,7 @@ public final class HeadersTest { .build(); assertEquals(singletonList( - new Challenge("Other", singletonMap(((String) null), "abc=="))), + new Challenge("Other", singletonMap(null, "abc=="))), HttpHeaders.parseChallenges(headers, "WWW-Authenticate")); } @@ -761,7 +776,7 @@ public final class HeadersTest { .build(); assertEquals(Arrays.asList( - new Challenge("Other", singletonMap((String) null, "abc=="))), + new Challenge("Other", singletonMap(null, "abc=="))), HttpHeaders.parseChallenges(headers, "WWW-Authenticate")); } @@ -780,7 +795,7 @@ public final class HeadersTest { .build(); assertEquals(Arrays.asList( - new Challenge("Digest", Collections.<String, String>emptyMap()), + new Challenge("Digest", Collections.emptyMap()), new Challenge("Basic", singletonMap("realm", "myrealm"))), HttpHeaders.parseChallenges(headers, "WWW-Authenticate")); } @@ -793,7 +808,7 @@ public final class HeadersTest { assertEquals(Arrays.asList( new Challenge("Basic", singletonMap("realm", "myrealm")), - new Challenge("Digest", Collections.<String, String>emptyMap())), + new Challenge("Digest", Collections.emptyMap())), HttpHeaders.parseChallenges(headers, "WWW-Authenticate")); } @@ -810,7 +825,7 @@ public final class HeadersTest { } @Test public void byteCount() { - assertEquals(0L, new Headers.Builder().build().byteCount()); + assertEquals(0L, Util.EMPTY_HEADERS.byteCount()); assertEquals(10L, new Headers.Builder() .add("abc", "def") .build() @@ -823,11 +838,12 @@ public final class HeadersTest { } @Test public void addDate() { - Date expected = new Date(0); + Date expected = new Date(0L); Headers headers = new Headers.Builder() .add("testDate", expected) .build(); assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", headers.get("testDate")); + assertEquals(new Date(0L), headers.getDate("testDate")); } @Test public void addDateNull() { @@ -841,13 +857,34 @@ public final class HeadersTest { } } + @Test public void addInstant() { + Instant expected = Instant.ofEpochMilli(0L); + Headers headers = new Headers.Builder() + .add("Test-Instant", expected) + .build(); + assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", headers.get("Test-Instant")); + assertEquals(expected, headers.getInstant("Test-Instant")); + } + + @Test public void addInstantNull() { + try { + new Headers.Builder() + .add("Test-Instant", (Instant) null) + .build(); + fail(); + } catch (NullPointerException expected) { + assertEquals("value for name Test-Instant == null", expected.getMessage()); + } + } + @Test public void setDate() { Date expected = new Date(1000); Headers headers = new Headers.Builder() - .add("testDate", new Date(0)) + .add("testDate", new Date(0L)) .set("testDate", expected) .build(); assertEquals("Thu, 01 Jan 1970 00:00:01 GMT", headers.get("testDate")); + assertEquals(expected, headers.getDate("testDate")); } @Test public void setDateNull() { @@ -860,4 +897,25 @@ public final class HeadersTest { assertEquals("value for name testDate == null", expected.getMessage()); } } + + @Test public void setInstant() { + Instant expected = Instant.ofEpochMilli(1000L); + Headers headers = new Headers.Builder() + .add("Test-Instant", Instant.ofEpochMilli(0L)) + .set("Test-Instant", expected) + .build(); + assertEquals("Thu, 01 Jan 1970 00:00:01 GMT", headers.get("Test-Instant")); + assertEquals(expected, headers.getInstant("Test-Instant")); + } + + @Test public void setInstantNull() { + try { + new Headers.Builder() + .set("Test-Instant", (Instant) null) + .build(); + fail(); + } catch (NullPointerException expected) { + assertEquals("value for name Test-Instant == null", expected.getMessage()); + } + } } diff --git a/okhttp-tests/src/test/java/okhttp3/HttpUrlTest.java b/okhttp-tests/src/test/java/okhttp3/HttpUrlTest.java index e780d8bd9b9d618af83b16e364c22db455167a6a..23a32d7e92b13d9c0fb062179daf794d403f4013 100644 --- a/okhttp-tests/src/test/java/okhttp3/HttpUrlTest.java +++ b/okhttp-tests/src/test/java/okhttp3/HttpUrlTest.java @@ -42,16 +42,16 @@ public final class HttpUrlTest { new Object[] { false } ); } - + @Parameterized.Parameter public boolean useGet; - + HttpUrl parse(String url) { return useGet ? HttpUrl.get(url) : HttpUrl.parse(url); } - + @Test public void parseTrimsAsciiWhitespace() throws Exception { HttpUrl expected = parse("http://host/"); assertEquals(expected, parse("http://host/\f\n\t \r")); // Leading. @@ -138,10 +138,10 @@ public final class HttpUrlTest { assertEquals(parse("http://host/a/b?query"), base.newBuilder("?query").build()); assertEquals(parse("http://host/a/b#fragment"), base.newBuilder("#fragment").build()); assertEquals(parse("http://host/a/b"), base.newBuilder("").build()); - assertEquals(null, base.newBuilder("ftp://b")); - assertEquals(null, base.newBuilder("ht+tp://b")); - assertEquals(null, base.newBuilder("ht-tp://b")); - assertEquals(null, base.newBuilder("ht.tp://b")); + assertNull(base.newBuilder("ftp://b")); + assertNull(base.newBuilder("ht+tp://b")); + assertNull(base.newBuilder("ht-tp://b")); + assertNull(base.newBuilder("ht.tp://b")); } @Test public void redactedUrl() { @@ -166,10 +166,10 @@ public final class HttpUrlTest { @Test public void resolveUnsupportedScheme() throws Exception { HttpUrl base = parse("http://a/"); - assertEquals(null, base.resolve("ftp://b")); - assertEquals(null, base.resolve("ht+tp://b")); - assertEquals(null, base.resolve("ht-tp://b")); - assertEquals(null, base.resolve("ht.tp://b")); + assertNull(base.resolve("ftp://b")); + assertNull(base.resolve("ht+tp://b")); + assertNull(base.resolve("ht-tp://b")); + assertNull(base.resolve("ht.tp://b")); } @Test public void resolveSchemeLikePath() throws Exception { @@ -183,7 +183,7 @@ public final class HttpUrlTest { /** https://tools.ietf.org/html/rfc3986#section-5.4.1 */ @Test public void rfc3886NormalExamples() { HttpUrl url = parse("http://a/b/c/d;p?q"); - assertEquals(null, url.resolve("g:h")); // No 'g:' scheme in HttpUrl. + assertNull(url.resolve("g:h")); // No 'g:' scheme in HttpUrl. assertEquals(parse("http://a/b/c/g"), url.resolve("g")); assertEquals(parse("http://a/b/c/g"), url.resolve("./g")); assertEquals(parse("http://a/b/c/g/"), url.resolve("g/")); @@ -534,6 +534,10 @@ public final class HttpUrlTest { "Invalid URL host: \"[0:0:0:0:0:1:255.255.255]\""); } + @Test public void hostIpv6Malformed() throws Exception { + assertInvalid("http://[::g]/", "Invalid URL host: \"[::g]\""); + } + @Test public void hostIpv6CanonicalForm() throws Exception { assertEquals("abcd:ef01:2345:6789:abcd:ef01:2345:6789", parse("http://[abcd:ef01:2345:6789:abcd:ef01:2345:6789]/").host()); @@ -554,10 +558,11 @@ public final class HttpUrlTest { assertEquals("1::", parse("http://[1:0:0:0:0:0:0:0]/").host()); assertEquals("::1", parse("http://[0:0:0:0:0:0:0:1]/").host()); assertEquals("::", parse("http://[0:0:0:0:0:0:0:0]/").host()); + assertEquals("192.168.1.254", parse("http://[::ffff:c0a8:1fe]/").host()); } /** The builder permits square braces but does not require them. */ - @Test public void hostIPv6Builder() throws Exception { + @Test public void hostIpv6Builder() throws Exception { HttpUrl base = parse("http://example.com/"); assertEquals("http://[::1]/", base.newBuilder().host("[::1]").build().toString()); assertEquals("http://[::1]/", base.newBuilder().host("[::0001]").build().toString()); @@ -571,7 +576,6 @@ public final class HttpUrlTest { assertEquals("0.0.0.0", parse("http://0.0.0.0/").host()); } - @Ignore("java.net.IDN strips trailing trailing dots on Java 7, but not on Java 8.") @Test public void hostWithTrailingDot() throws Exception { assertEquals("host.", parse("http://host./").host()); } @@ -794,8 +798,8 @@ public final class HttpUrlTest { assertEquals("host", url.host()); assertEquals(80, url.port()); assertEquals("/", url.encodedPath()); - assertEquals(null, url.query()); - assertEquals(null, url.fragment()); + assertNull(url.query()); + assertNull(url.fragment()); } @Test public void fullUrlComposition() throws Exception { @@ -1368,7 +1372,7 @@ public final class HttpUrlTest { @Test public void fromJavaNetUrlUnsupportedScheme() throws Exception { URL javaNetUrl = new URL("mailto:user@example.com"); - assertEquals(null, HttpUrl.get(javaNetUrl)); + assertNull(HttpUrl.get(javaNetUrl)); } @Test public void fromUri() throws Exception { @@ -1379,12 +1383,12 @@ public final class HttpUrlTest { @Test public void fromUriUnsupportedScheme() throws Exception { URI uri = new URI("mailto:user@example.com"); - assertEquals(null, HttpUrl.get(uri)); + assertNull(HttpUrl.get(uri)); } @Test public void fromUriPartial() throws Exception { URI uri = new URI("/path"); - assertEquals(null, HttpUrl.get(uri)); + assertNull(HttpUrl.get(uri)); } @Test public void composeQueryWithComponents() throws Exception { @@ -1414,7 +1418,7 @@ public final class HttpUrlTest { .removeAllQueryParameters("a+=& b") .build(); assertEquals("http://host/", url.toString()); - assertEquals(null, url.queryParameter("a+=& b")); + assertNull(url.queryParameter("a+=& b")); } @Test public void composeQueryRemoveEncodedQueryParameter() throws Exception { @@ -1423,7 +1427,7 @@ public final class HttpUrlTest { .removeAllEncodedQueryParameters("a+=& b") .build(); assertEquals("http://host/", url.toString()); - assertEquals(null, url.queryParameter("a =& b")); + assertNull(url.queryParameter("a =& b")); } @Test public void composeQuerySetQueryParameter() throws Exception { @@ -1469,7 +1473,7 @@ public final class HttpUrlTest { .build(); assertEquals(1, url.querySize()); assertEquals("", url.queryParameterName(0)); - assertEquals(null, url.queryParameterValue(0)); + assertNull(url.queryParameterValue(0)); } @Test public void ampersandQueryIsTwoNameValuePairsWithEmptyKeys() throws Exception { @@ -1478,9 +1482,9 @@ public final class HttpUrlTest { .build(); assertEquals(2, url.querySize()); assertEquals("", url.queryParameterName(0)); - assertEquals(null, url.queryParameterValue(0)); + assertNull(url.queryParameterValue(0)); assertEquals("", url.queryParameterName(1)); - assertEquals(null, url.queryParameterValue(1)); + assertNull(url.queryParameterValue(1)); } @Test public void removeAllDoesNotRemoveQueryIfNoParametersWereRemoved() throws Exception { @@ -1496,9 +1500,9 @@ public final class HttpUrlTest { assertEquals(3, url.querySize()); assertEquals(new LinkedHashSet<>(Arrays.asList("foo", "bar", "baz")), url.queryParameterNames()); - assertEquals(null, url.queryParameterValue(0)); - assertEquals(null, url.queryParameterValue(1)); - assertEquals(null, url.queryParameterValue(2)); + assertNull(url.queryParameterValue(0)); + assertNull(url.queryParameterValue(1)); + assertNull(url.queryParameterValue(2)); assertEquals(singletonList((String) null), url.queryParameterValues("foo")); assertEquals(singletonList((String) null), url.queryParameterValues("bar")); assertEquals(singletonList((String) null), url.queryParameterValues("baz")); @@ -1581,8 +1585,8 @@ public final class HttpUrlTest { .fragment(null) .build(); assertEquals("http://host/", url.toString()); - assertEquals(null, url.fragment()); - assertEquals(null, url.encodedFragment()); + assertNull(url.fragment()); + assertNull(url.encodedFragment()); } @Test public void clearEncodedFragment() throws Exception { @@ -1591,8 +1595,8 @@ public final class HttpUrlTest { .encodedFragment(null) .build(); assertEquals("http://host/", url.toString()); - assertEquals(null, url.fragment()); - assertEquals(null, url.encodedFragment()); + assertNull(url.fragment()); + assertNull(url.encodedFragment()); } @Test public void topPrivateDomain() { diff --git a/okhttp-tests/src/test/java/okhttp3/InterceptorTest.java b/okhttp-tests/src/test/java/okhttp3/InterceptorTest.java index a1ad679f347539aafbb98fd18b2d20c9b2e3b202..7de63fc13870aabee04ae3e96a81fb8a32cde6ab 100644 --- a/okhttp-tests/src/test/java/okhttp3/InterceptorTest.java +++ b/okhttp-tests/src/test/java/okhttp3/InterceptorTest.java @@ -16,6 +16,8 @@ package okhttp3; import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; import java.net.SocketTimeoutException; import java.util.Arrays; import java.util.Locale; @@ -62,7 +64,7 @@ public final class InterceptorTest { .url("https://localhost:1/") .build(); - final Response interceptorResponse = new Response.Builder() + Response interceptorResponse = new Response.Builder() .request(request) .protocol(Protocol.HTTP_1_1) .code(200) @@ -71,11 +73,8 @@ public final class InterceptorTest { .build(); client = client.newBuilder() - .addInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - return interceptorResponse; - } - }).build(); + .addInterceptor(chain -> interceptorResponse) + .build(); Response response = client.newCall(request).execute(); assertSame(interceptorResponse, response); @@ -84,17 +83,13 @@ public final class InterceptorTest { @Test public void networkInterceptorsCannotShortCircuitResponses() throws Exception { server.enqueue(new MockResponse().setResponseCode(500)); - Interceptor interceptor = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - return new Response.Builder() - .request(chain.request()) - .protocol(Protocol.HTTP_1_1) - .code(200) - .message("Intercepted!") - .body(ResponseBody.create(MediaType.get("text/plain; charset=utf-8"), "abc")) - .build(); - } - }; + Interceptor interceptor = chain -> new Response.Builder() + .request(chain.request()) + .protocol(Protocol.HTTP_1_1) + .code(200) + .message("Intercepted!") + .body(ResponseBody.create(MediaType.get("text/plain; charset=utf-8"), "abc")) + .build(); client = client.newBuilder() .addNetworkInterceptor(interceptor) .build(); @@ -116,11 +111,9 @@ public final class InterceptorTest { server.enqueue(new MockResponse()); server.enqueue(new MockResponse()); - Interceptor interceptor = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - chain.proceed(chain.request()); - return chain.proceed(chain.request()); - } + Interceptor interceptor = chain -> { + chain.proceed(chain.request()); + return chain.proceed(chain.request()); }; client = client.newBuilder() .addNetworkInterceptor(interceptor) @@ -142,15 +135,13 @@ public final class InterceptorTest { @Test public void networkInterceptorsCannotChangeServerAddress() throws Exception { server.enqueue(new MockResponse().setResponseCode(500)); - Interceptor interceptor = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - Address address = chain.connection().route().address(); - String sameHost = address.url().host(); - int differentPort = address.url().port() + 1; - return chain.proceed(chain.request().newBuilder() - .url("http://" + sameHost + ":" + differentPort + "/") - .build()); - } + Interceptor interceptor = chain -> { + Address address = chain.connection().route().address(); + String sameHost = address.url().host(); + int differentPort = address.url().port() + 1; + return chain.proceed(chain.request().newBuilder() + .url("http://" + sameHost + ":" + differentPort + "/") + .build()); }; client = client.newBuilder() .addNetworkInterceptor(interceptor) @@ -172,12 +163,10 @@ public final class InterceptorTest { @Test public void networkInterceptorsHaveConnectionAccess() throws Exception { server.enqueue(new MockResponse()); - Interceptor interceptor = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - Connection connection = chain.connection(); - assertNotNull(connection); - return chain.proceed(chain.request()); - } + Interceptor interceptor = chain -> { + Connection connection = chain.connection(); + assertNotNull(connection); + return chain.proceed(chain.request()); }; client = client.newBuilder() .addNetworkInterceptor(interceptor) @@ -194,20 +183,18 @@ public final class InterceptorTest { .setBody(gzip("abcabcabc")) .addHeader("Content-Encoding: gzip")); - Interceptor interceptor = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - // The network request has everything: User-Agent, Host, Accept-Encoding. - Request networkRequest = chain.request(); - assertNotNull(networkRequest.header("User-Agent")); - assertEquals(server.getHostName() + ":" + server.getPort(), - networkRequest.header("Host")); - assertNotNull(networkRequest.header("Accept-Encoding")); - - // The network response also has everything, including the raw gzipped content. - Response networkResponse = chain.proceed(networkRequest); - assertEquals("gzip", networkResponse.header("Content-Encoding")); - return networkResponse; - } + Interceptor interceptor = chain -> { + // The network request has everything: User-Agent, Host, Accept-Encoding. + Request networkRequest = chain.request(); + assertNotNull(networkRequest.header("User-Agent")); + assertEquals(server.getHostName() + ":" + server.getPort(), + networkRequest.header("Host")); + assertNotNull(networkRequest.header("Accept-Encoding")); + + // The network response also has everything, including the raw gzipped content. + Response networkResponse = chain.proceed(networkRequest); + assertEquals("gzip", networkResponse.header("Content-Encoding")); + return networkResponse; }; client = client.newBuilder() .addNetworkInterceptor(interceptor) @@ -231,17 +218,15 @@ public final class InterceptorTest { @Test public void networkInterceptorsCanChangeRequestMethodFromGetToPost() throws Exception { server.enqueue(new MockResponse()); - Interceptor interceptor = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - Request originalRequest = chain.request(); - MediaType mediaType = MediaType.get("text/plain"); - RequestBody body = RequestBody.create(mediaType, "abc"); - return chain.proceed(originalRequest.newBuilder() - .method("POST", body) - .header("Content-Type", mediaType.toString()) - .header("Content-Length", Long.toString(body.contentLength())) - .build()); - } + Interceptor interceptor = chain -> { + Request originalRequest = chain.request(); + MediaType mediaType = MediaType.get("text/plain"); + RequestBody body = RequestBody.create(mediaType, "abc"); + return chain.proceed(originalRequest.newBuilder() + .method("POST", body) + .header("Content-Type", mediaType.toString()) + .header("Content-Length", Long.toString(body.contentLength())) + .build()); }; client = client.newBuilder() .addNetworkInterceptor(interceptor) @@ -270,14 +255,12 @@ public final class InterceptorTest { private void rewriteRequestToServer(boolean network) throws Exception { server.enqueue(new MockResponse()); - addInterceptor(network, new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - Request originalRequest = chain.request(); - return chain.proceed(originalRequest.newBuilder() - .method("POST", uppercase(originalRequest.body())) - .addHeader("OkHttp-Intercepted", "yep") - .build()); - } + addInterceptor(network, chain -> { + Request originalRequest = chain.request(); + return chain.proceed(originalRequest.newBuilder() + .method("POST", uppercase(originalRequest.body())) + .addHeader("OkHttp-Intercepted", "yep") + .build()); }); Request request = new Request.Builder() @@ -308,14 +291,12 @@ public final class InterceptorTest { .addHeader("Original-Header: foo") .setBody("abc")); - addInterceptor(network, new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - Response originalResponse = chain.proceed(chain.request()); - return originalResponse.newBuilder() - .body(uppercase(originalResponse.body())) - .addHeader("OkHttp-Intercepted", "yep") - .build(); - } + addInterceptor(network, chain -> { + Response originalResponse = chain.proceed(chain.request()); + return originalResponse.newBuilder() + .body(uppercase(originalResponse.body())) + .addHeader("OkHttp-Intercepted", "yep") + .build(); }); Request request = new Request.Builder() @@ -339,27 +320,23 @@ public final class InterceptorTest { private void multipleInterceptors(boolean network) throws Exception { server.enqueue(new MockResponse()); - addInterceptor(network, new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - Request originalRequest = chain.request(); - Response originalResponse = chain.proceed(originalRequest.newBuilder() - .addHeader("Request-Interceptor", "Android") // 1. Added first. - .build()); - return originalResponse.newBuilder() - .addHeader("Response-Interceptor", "Donut") // 4. Added last. - .build(); - } + addInterceptor(network, chain -> { + Request originalRequest = chain.request(); + Response originalResponse = chain.proceed(originalRequest.newBuilder() + .addHeader("Request-Interceptor", "Android") // 1. Added first. + .build()); + return originalResponse.newBuilder() + .addHeader("Response-Interceptor", "Donut") // 4. Added last. + .build(); }); - addInterceptor(network, new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - Request originalRequest = chain.request(); - Response originalResponse = chain.proceed(originalRequest.newBuilder() - .addHeader("Request-Interceptor", "Bob") // 2. Added second. - .build()); - return originalResponse.newBuilder() - .addHeader("Response-Interceptor", "Cupcake") // 3. Added third. - .build(); - } + addInterceptor(network, chain -> { + Request originalRequest = chain.request(); + Response originalResponse = chain.proceed(originalRequest.newBuilder() + .addHeader("Request-Interceptor", "Bob") // 2. Added second. + .build()); + return originalResponse.newBuilder() + .addHeader("Response-Interceptor", "Cupcake") // 3. Added third. + .build(); }); Request request = new Request.Builder() @@ -386,13 +363,11 @@ public final class InterceptorTest { private void asyncInterceptors(boolean network) throws Exception { server.enqueue(new MockResponse()); - addInterceptor(network, new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - Response originalResponse = chain.proceed(chain.request()); - return originalResponse.newBuilder() - .addHeader("OkHttp-Intercepted", "yep") - .build(); - } + addInterceptor(network, chain -> { + Response originalResponse = chain.proceed(chain.request()); + return originalResponse.newBuilder() + .addHeader("OkHttp-Intercepted", "yep") + .build(); }); Request request = new Request.Builder() @@ -410,13 +385,12 @@ public final class InterceptorTest { server.enqueue(new MockResponse().setBody("b")); client = client.newBuilder() - .addInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - Response response1 = chain.proceed(chain.request()); - response1.body().close(); - return chain.proceed(chain.request()); - } - }).build(); + .addInterceptor(chain -> { + Response response1 = chain.proceed(chain.request()); + response1.body().close(); + return chain.proceed(chain.request()); + }) + .build(); Request request = new Request.Builder() .url(server.url("/")) @@ -432,19 +406,18 @@ public final class InterceptorTest { server.enqueue(new MockResponse().setBody("b")); // Fetched directly. client = client.newBuilder() - .addInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - if (chain.request().url().encodedPath().equals("/b")) { - Request requestA = new Request.Builder() - .url(server.url("/a")) - .build(); - Response responseA = client.newCall(requestA).execute(); - assertEquals("a", responseA.body().string()); - } - - return chain.proceed(chain.request()); + .addInterceptor(chain -> { + if (chain.request().url().encodedPath().equals("/b")) { + Request requestA = new Request.Builder() + .url(server.url("/a")) + .build(); + Response responseA = client.newCall(requestA).execute(); + assertEquals("a", responseA.body().string()); } - }).build(); + + return chain.proceed(chain.request()); + }) + .build(); Request requestB = new Request.Builder() .url(server.url("/b")) @@ -459,25 +432,24 @@ public final class InterceptorTest { server.enqueue(new MockResponse().setBody("b")); // Fetched directly. client = client.newBuilder() - .addInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - if (chain.request().url().encodedPath().equals("/b")) { - Request requestA = new Request.Builder() - .url(server.url("/a")) - .build(); - - try { - RecordingCallback callbackA = new RecordingCallback(); - client.newCall(requestA).enqueue(callbackA); - callbackA.await(requestA.url()).assertBody("a"); - } catch (Exception e) { - throw new RuntimeException(e); - } + .addInterceptor(chain -> { + if (chain.request().url().encodedPath().equals("/b")) { + Request requestA = new Request.Builder() + .url(server.url("/a")) + .build(); + + try { + RecordingCallback callbackA = new RecordingCallback(); + client.newCall(requestA).enqueue(callbackA); + callbackA.await(requestA.url()).assertBody("a"); + } catch (Exception e) { + throw new RuntimeException(e); } - - return chain.proceed(chain.request()); } - }).build(); + + return chain.proceed(chain.request()); + }) + .build(); Request requestB = new Request.Builder() .url(server.url("/b")) @@ -500,11 +472,7 @@ public final class InterceptorTest { * with it. */ private void interceptorThrowsRuntimeExceptionSynchronous(boolean network) throws Exception { - addInterceptor(network, new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - throw new RuntimeException("boom!"); - } - }); + addInterceptor(network, chain -> { throw new RuntimeException("boom!"); }); Request request = new Request.Builder() .url(server.url("/")) @@ -521,12 +489,12 @@ public final class InterceptorTest { @Test public void networkInterceptorModifiedRequestIsReturned() throws IOException { server.enqueue(new MockResponse()); - Interceptor modifyHeaderInterceptor = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - return chain.proceed(chain.request().newBuilder() - .header("User-Agent", "intercepted request") - .build()); - } + Interceptor modifyHeaderInterceptor = chain -> { + Request modifiedRequest = chain.request() + .newBuilder() + .header("User-Agent", "intercepted request") + .build(); + return chain.proceed(modifiedRequest); }; client = client.newBuilder() @@ -557,11 +525,7 @@ public final class InterceptorTest { * exception goes to the uncaught exception handler. */ private void interceptorThrowsRuntimeExceptionAsynchronous(boolean network) throws Exception { - addInterceptor(network, new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - throw new RuntimeException("boom!"); - } - }); + addInterceptor(network, chain -> { throw new RuntimeException("boom!"); }); ExceptionCatchingExecutor executor = new ExceptionCatchingExecutor(); client = client.newBuilder() @@ -579,11 +543,9 @@ public final class InterceptorTest { @Test public void applicationInterceptorReturnsNull() throws Exception { server.enqueue(new MockResponse()); - Interceptor interceptor = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - chain.proceed(chain.request()); - return null; - } + Interceptor interceptor = chain -> { + chain.proceed(chain.request()); + return null; }; client = client.newBuilder() .addInterceptor(interceptor) @@ -608,11 +570,9 @@ public final class InterceptorTest { @Test public void networkInterceptorReturnsNull() throws Exception { server.enqueue(new MockResponse()); - Interceptor interceptor = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - chain.proceed(chain.request()); - return null; - } + Interceptor interceptor = chain -> { + chain.proceed(chain.request()); + return null; }; client = client.newBuilder() .addNetworkInterceptor(interceptor) @@ -639,12 +599,10 @@ public final class InterceptorTest { .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END) .addHeader("Connection", "Close")); - Interceptor interceptor = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - Response response = chain.proceed(chain.request()); - assertNotNull(chain.connection()); - return response; - } + Interceptor interceptor = chain -> { + Response response = chain.proceed(chain.request()); + assertNotNull(chain.connection()); + return response; }; client = client.newBuilder() @@ -662,10 +620,11 @@ public final class InterceptorTest { @Test public void applicationInterceptorResponseMustHaveBody() throws Exception { server.enqueue(new MockResponse()); - Interceptor interceptor = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - return chain.proceed(chain.request()).newBuilder().body(null).build(); - } + Interceptor interceptor = chain -> { + Response response = chain.proceed(chain.request()); + return response.newBuilder() + .body(null) + .build(); }; client = client.newBuilder() .addInterceptor(interceptor) @@ -686,10 +645,11 @@ public final class InterceptorTest { @Test public void networkInterceptorResponseMustHaveBody() throws Exception { server.enqueue(new MockResponse()); - Interceptor interceptor = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - return chain.proceed(chain.request()).newBuilder().body(null).build(); - } + Interceptor interceptor = chain -> { + Response response = chain.proceed(chain.request()); + return response.newBuilder() + .body(null) + .build(); }; client = client.newBuilder() .addNetworkInterceptor(interceptor) @@ -708,33 +668,38 @@ public final class InterceptorTest { } @Test public void connectTimeout() throws Exception { - Interceptor interceptor1 = new Interceptor() { - @Override public Response intercept(Chain chainA) throws IOException { - assertEquals(5000, chainA.connectTimeoutMillis()); + Interceptor interceptor1 = chainA -> { + assertEquals(5000, chainA.connectTimeoutMillis()); - Chain chainB = chainA.withConnectTimeout(100, TimeUnit.MILLISECONDS); - assertEquals(100, chainB.connectTimeoutMillis()); + Interceptor.Chain chainB = chainA.withConnectTimeout(100, TimeUnit.MILLISECONDS); + assertEquals(100, chainB.connectTimeoutMillis()); - return chainB.proceed(chainA.request()); - } + return chainB.proceed(chainA.request()); }; - Interceptor interceptor2 = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - assertEquals(100, chain.connectTimeoutMillis()); - return chain.proceed(chain.request()); - } + Interceptor interceptor2 = chain -> { + assertEquals(100, chain.connectTimeoutMillis()); + return chain.proceed(chain.request()); }; + ServerSocket serverSocket = new ServerSocket(0, 1); + // Fill backlog queue with this request so subsequent requests will be blocked. + new Socket().connect(serverSocket.getLocalSocketAddress()); + client = client.newBuilder() .connectTimeout(5, TimeUnit.SECONDS) .addInterceptor(interceptor1) .addInterceptor(interceptor2) .build(); - Request request1 = new Request.Builder() - .url("http://" + TestUtil.UNREACHABLE_ADDRESS) - .build(); + Request request1 = + new Request.Builder() + .url( + "http://" + + serverSocket.getInetAddress().getCanonicalHostName() + + ":" + + serverSocket.getLocalPort()) + .build(); Call call = client.newCall(request1); try { @@ -742,25 +707,23 @@ public final class InterceptorTest { fail(); } catch (SocketTimeoutException expected) { } + + serverSocket.close(); } @Test public void chainWithReadTimeout() throws Exception { - Interceptor interceptor1 = new Interceptor() { - @Override public Response intercept(Chain chainA) throws IOException { - assertEquals(5000, chainA.readTimeoutMillis()); + Interceptor interceptor1 = chainA -> { + assertEquals(5000, chainA.readTimeoutMillis()); - Chain chainB = chainA.withReadTimeout(100, TimeUnit.MILLISECONDS); - assertEquals(100, chainB.readTimeoutMillis()); + Interceptor.Chain chainB = chainA.withReadTimeout(100, TimeUnit.MILLISECONDS); + assertEquals(100, chainB.readTimeoutMillis()); - return chainB.proceed(chainA.request()); - } + return chainB.proceed(chainA.request()); }; - Interceptor interceptor2 = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - assertEquals(100, chain.readTimeoutMillis()); - return chain.proceed(chain.request()); - } + Interceptor interceptor2 = chain -> { + assertEquals(100, chain.readTimeoutMillis()); + return chain.proceed(chain.request()); }; client = client.newBuilder() @@ -787,22 +750,18 @@ public final class InterceptorTest { } @Test public void chainWithWriteTimeout() throws Exception { - Interceptor interceptor1 = new Interceptor() { - @Override public Response intercept(Chain chainA) throws IOException { - assertEquals(5000, chainA.writeTimeoutMillis()); + Interceptor interceptor1 = chainA -> { + assertEquals(5000, chainA.writeTimeoutMillis()); - Chain chainB = chainA.withWriteTimeout(100, TimeUnit.MILLISECONDS); - assertEquals(100, chainB.writeTimeoutMillis()); + Interceptor.Chain chainB = chainA.withWriteTimeout(100, TimeUnit.MILLISECONDS); + assertEquals(100, chainB.writeTimeoutMillis()); - return chainB.proceed(chainA.request()); - } + return chainB.proceed(chainA.request()); }; - Interceptor interceptor2 = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - assertEquals(100, chain.writeTimeoutMillis()); - return chain.proceed(chain.request()); - } + Interceptor interceptor2 = chain -> { + assertEquals(100, chain.writeTimeoutMillis()); + return chain.proceed(chain.request()); }; client = client.newBuilder() @@ -830,19 +789,17 @@ public final class InterceptorTest { } @Test public void chainCanCancelCall() throws Exception { - final AtomicReference<Call> callRef = new AtomicReference<>(); + AtomicReference<Call> callRef = new AtomicReference<>(); - Interceptor interceptor = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - Call call = chain.call(); - callRef.set(call); + Interceptor interceptor = chain -> { + Call call = chain.call(); + callRef.set(call); - assertFalse(call.isCanceled()); - call.cancel(); - assertTrue(call.isCanceled()); + assertFalse(call.isCanceled()); + call.cancel(); + assertTrue(call.isCanceled()); - return chain.proceed(chain.request()); - } + return chain.proceed(chain.request()); }; client = client.newBuilder() @@ -863,7 +820,7 @@ public final class InterceptorTest { assertSame(call, callRef.get()); } - private RequestBody uppercase(final RequestBody original) { + private RequestBody uppercase(RequestBody original) { return new RequestBody() { @Override public MediaType contentType() { return original.contentType(); @@ -882,7 +839,7 @@ public final class InterceptorTest { }; } - private Sink uppercase(final BufferedSink original) { + private Sink uppercase(BufferedSink original) { return new ForwardingSink(original) { @Override public void write(Buffer source, long byteCount) throws IOException { original.writeUtf8(source.readUtf8(byteCount).toUpperCase(Locale.US)); @@ -895,7 +852,7 @@ public final class InterceptorTest { Okio.buffer(uppercase(original.source()))); } - private static Source uppercase(final Source original) { + private static Source uppercase(Source original) { return new ForwardingSource(original) { @Override public long read(Buffer sink, long byteCount) throws IOException { Buffer mixedCase = new Buffer(); @@ -929,17 +886,15 @@ public final class InterceptorTest { private final BlockingQueue<Exception> exceptions = new LinkedBlockingQueue<>(); public ExceptionCatchingExecutor() { - super(1, 1, 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); + super(1, 1, 0, TimeUnit.SECONDS, new SynchronousQueue<>()); } - @Override public void execute(final Runnable runnable) { - super.execute(new Runnable() { - @Override public void run() { - try { - runnable.run(); - } catch (Exception e) { - exceptions.add(e); - } + @Override public void execute(Runnable runnable) { + super.execute(() -> { + try { + runnable.run(); + } catch (Exception e) { + exceptions.add(e); } }); } diff --git a/okhttp-tests/src/test/java/okhttp3/MediaTypeTest.java b/okhttp-tests/src/test/java/okhttp3/MediaTypeTest.java index f0b194713dc90dd597b0cb86aba704b2c179674e..1520bcaf8e966b994788c30add9111346390c03c 100644 --- a/okhttp-tests/src/test/java/okhttp3/MediaTypeTest.java +++ b/okhttp-tests/src/test/java/okhttp3/MediaTypeTest.java @@ -19,14 +19,13 @@ package okhttp3; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collection; -import okhttp3.internal.Util; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** @@ -60,7 +59,7 @@ public class MediaTypeTest { assertEquals("plain", mediaType.subtype()); assertEquals("UTF-8", mediaType.charset().name()); assertEquals("text/plain;boundary=foo;charset=utf-8", mediaType.toString()); - assertTrue(mediaType.equals(parse("text/plain;boundary=foo;charset=utf-8"))); + assertEquals(mediaType, parse("text/plain;boundary=foo;charset=utf-8")); assertEquals(mediaType.hashCode(), parse("text/plain;boundary=foo;charset=utf-8").hashCode()); } @@ -188,11 +187,11 @@ public class MediaTypeTest { @Test public void testDefaultCharset() throws Exception { MediaType noCharset = parse("text/plain"); - assertEquals("UTF-8", noCharset.charset(Util.UTF_8).name()); + assertEquals("UTF-8", noCharset.charset(UTF_8).name()); assertEquals("US-ASCII", noCharset.charset(Charset.forName("US-ASCII")).name()); MediaType charset = parse("text/plain; charset=iso-8859-1"); - assertEquals("ISO-8859-1", charset.charset(Util.UTF_8).name()); + assertEquals("ISO-8859-1", charset.charset(UTF_8).name()); assertEquals("ISO-8859-1", charset.charset(Charset.forName("US-ASCII")).name()); } diff --git a/okhttp-tests/src/test/java/okhttp3/MultipartBodyTest.java b/okhttp-tests/src/test/java/okhttp3/MultipartBodyTest.java index f936d56a80475096eeb1fd8b49c3ad8a884dd8dd..7696ed9978fac77f0c55cb66ca6c55293689b92c 100644 --- a/okhttp-tests/src/test/java/okhttp3/MultipartBodyTest.java +++ b/okhttp-tests/src/test/java/okhttp3/MultipartBodyTest.java @@ -20,7 +20,7 @@ import okio.Buffer; import okio.BufferedSink; import org.junit.Test; -import static okhttp3.internal.Util.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -271,4 +271,25 @@ public final class MultipartBodyTest { assertEquals(Headers.of("Foo", "Bar"), part1.headers()); assertEquals("Baz", part1Buffer.readUtf8()); } + + @Test public void nonAsciiFilename() throws Exception { + String expected = "" + + "--AaB03x\r\n" + + "Content-Disposition: form-data; name=\"attachment\"; filename=\"resumé.pdf\"\r\n" + + "Content-Type: application/pdf; charset=utf-8\r\n" + + "Content-Length: 17\r\n" + + "\r\n" + + "Jesse’s Resumé\r\n" + + "--AaB03x--\r\n"; + + MultipartBody body = new MultipartBody.Builder("AaB03x") + .setType(MultipartBody.FORM) + .addFormDataPart("attachment", "resumé.pdf", + RequestBody.create(MediaType.parse("application/pdf"), "Jesse’s Resumé")) + .build(); + + Buffer buffer = new Buffer(); + body.writeTo(buffer); + assertEquals(expected, buffer.readUtf8()); + } } diff --git a/okhttp-tests/src/test/java/okhttp3/OkHttpClientTest.java b/okhttp-tests/src/test/java/okhttp3/OkHttpClientTest.java index b4ee085371a3bcce07bb2f1d255d130190b1874b..caf43a52db64dcd85087909afc16c71008bc31fb 100644 --- a/okhttp-tests/src/test/java/okhttp3/OkHttpClientTest.java +++ b/okhttp-tests/src/test/java/okhttp3/OkHttpClientTest.java @@ -15,13 +15,13 @@ */ package okhttp3; -import java.io.IOException; import java.net.CookieHandler; import java.net.CookieManager; import java.net.ProxySelector; import java.net.ResponseCache; import java.util.Arrays; import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLSocketFactory; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.After; @@ -97,11 +97,7 @@ public final class OkHttpClientTest { } @Test public void clonedInterceptorsListsAreIndependent() throws Exception { - Interceptor interceptor = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - return chain.proceed(chain.request()); - } - }; + Interceptor interceptor = chain -> chain.proceed(chain.request()); OkHttpClient original = defaultClient(); original.newBuilder() .addInterceptor(interceptor) @@ -230,4 +226,13 @@ public final class OkHttpClientTest { Response response = client.newCall(request).execute(); assertEquals("abc", response.body().string()); } + + @Test public void sslSocketFactorySetAsSocketFactory() throws Exception { + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + try { + builder.socketFactory(SSLSocketFactory.getDefault()); + fail(); + } catch (IllegalArgumentException expected) { + } + } } diff --git a/okhttp-tests/src/test/java/okhttp3/RecordedResponse.java b/okhttp-tests/src/test/java/okhttp3/RecordedResponse.java index d34380cc4f0220832974bc75a47c93d4275354e8..715cbda3831838511cb612e845acff9c87256490 100644 --- a/okhttp-tests/src/test/java/okhttp3/RecordedResponse.java +++ b/okhttp-tests/src/test/java/okhttp3/RecordedResponse.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; +import javax.annotation.Nullable; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -31,13 +32,13 @@ import static org.junit.Assert.assertTrue; */ public final class RecordedResponse { public final Request request; - public final Response response; - public final WebSocket webSocket; - public final String body; - public final IOException failure; + public final @Nullable Response response; + public final @Nullable WebSocket webSocket; + public final @Nullable String body; + public final @Nullable IOException failure; - public RecordedResponse(Request request, Response response, WebSocket webSocket, String body, - IOException failure) { + public RecordedResponse(Request request, @Nullable Response response, + @Nullable WebSocket webSocket, @Nullable String body, @Nullable IOException failure) { this.request = request; this.response = response; this.webSocket = webSocket; diff --git a/okhttp-tests/src/test/java/okhttp3/RecordingEventListener.java b/okhttp-tests/src/test/java/okhttp3/RecordingEventListener.java index 6799300cdfc7d75bc70821a13290852ec72179d6..ee26e6819f5962d692f2785c37eee549ffac1500 100644 --- a/okhttp-tests/src/test/java/okhttp3/RecordingEventListener.java +++ b/okhttp-tests/src/test/java/okhttp3/RecordingEventListener.java @@ -217,7 +217,7 @@ public final class RecordingEventListener extends EventListener { this.inetAddressList = inetAddressList; } - @Nullable @Override public CallEvent closes() { + @Override public @Nullable CallEvent closes() { return new DnsStart(call, domainName); } } @@ -265,7 +265,7 @@ public final class RecordingEventListener extends EventListener { this.ioe = ioe; } - @Nullable @Override public CallEvent closes() { + @Override public @Nullable CallEvent closes() { return new ConnectStart(call, inetSocketAddress, proxy); } } @@ -284,7 +284,7 @@ public final class RecordingEventListener extends EventListener { this.handshake = handshake; } - @Nullable @Override public CallEvent closes() { + @Override public @Nullable CallEvent closes() { return new SecureConnectStart(call); } } @@ -306,7 +306,7 @@ public final class RecordingEventListener extends EventListener { this.connection = connection; } - @Nullable @Override public CallEvent closes() { + @Override public @Nullable CallEvent closes() { return new ConnectionAcquired(call, connection); } } @@ -322,7 +322,7 @@ public final class RecordingEventListener extends EventListener { super(call); } - @Nullable @Override public CallEvent closes() { + @Override public @Nullable CallEvent closes() { return new CallStart(call); } } @@ -350,7 +350,7 @@ public final class RecordingEventListener extends EventListener { this.headerLength = headerLength; } - @Nullable @Override public CallEvent closes() { + @Override public @Nullable CallEvent closes() { return new RequestHeadersStart(call); } } @@ -369,7 +369,7 @@ public final class RecordingEventListener extends EventListener { this.bytesWritten = bytesWritten; } - @Nullable @Override public CallEvent closes() { + @Override public @Nullable CallEvent closes() { return new RequestBodyStart(call); } } @@ -388,7 +388,7 @@ public final class RecordingEventListener extends EventListener { this.headerLength = headerLength; } - @Nullable @Override public CallEvent closes() { + @Override public @Nullable CallEvent closes() { return new RequestHeadersStart(call); } } @@ -407,7 +407,7 @@ public final class RecordingEventListener extends EventListener { this.bytesRead = bytesRead; } - @Nullable @Override public CallEvent closes() { + @Override public @Nullable CallEvent closes() { return new ResponseBodyStart(call); } } diff --git a/okhttp-tests/src/test/java/okhttp3/RequestTest.java b/okhttp-tests/src/test/java/okhttp3/RequestTest.java index 50b7bcb53d73e8605ea885cd1ce7e43e8c3a4b8e..a335abc2dc6591c9324a6b1c651895f30abd3573 100644 --- a/okhttp-tests/src/test/java/okhttp3/RequestTest.java +++ b/okhttp-tests/src/test/java/okhttp3/RequestTest.java @@ -22,10 +22,10 @@ import java.net.URI; import java.util.Arrays; import java.util.Collections; import java.util.UUID; -import okhttp3.internal.Util; import okio.Buffer; import org.junit.Test; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; @@ -34,7 +34,7 @@ import static org.junit.Assert.fail; public final class RequestTest { @Test public void string() throws Exception { MediaType contentType = MediaType.get("text/plain; charset=utf-8"); - RequestBody body = RequestBody.create(contentType, "abc".getBytes(Util.UTF_8)); + RequestBody body = RequestBody.create(contentType, "abc".getBytes(UTF_8)); assertEquals(contentType, body.contentType()); assertEquals(3, body.contentLength()); assertEquals("616263", bodyToHex(body)); @@ -59,7 +59,7 @@ public final class RequestTest { @Test public void byteArray() throws Exception { MediaType contentType = MediaType.get("text/plain"); - RequestBody body = RequestBody.create(contentType, "abc".getBytes(Util.UTF_8)); + RequestBody body = RequestBody.create(contentType, "abc".getBytes(UTF_8)); assertEquals(contentType, body.contentType()); assertEquals(3, body.contentLength()); assertEquals("616263", bodyToHex(body)); @@ -68,7 +68,7 @@ public final class RequestTest { @Test public void byteArrayRange() throws Exception { MediaType contentType = MediaType.get("text/plain"); - RequestBody body = RequestBody.create(contentType, ".abcd".getBytes(Util.UTF_8), 1, 3); + RequestBody body = RequestBody.create(contentType, ".abcd".getBytes(UTF_8), 1, 3); assertEquals(contentType, body.contentType()); assertEquals(3, body.contentLength()); assertEquals("616263", bodyToHex(body)); diff --git a/okhttp-tests/src/test/java/okhttp3/ResponseBodyTest.java b/okhttp-tests/src/test/java/okhttp3/ResponseBodyTest.java index 40f994c5266b9133c8ce28d5925e2ff4bb031c22..a64ac17d52129d49267f06882de016aa6fa6bce8 100644 --- a/okhttp-tests/src/test/java/okhttp3/ResponseBodyTest.java +++ b/okhttp-tests/src/test/java/okhttp3/ResponseBodyTest.java @@ -27,6 +27,7 @@ import okio.ForwardingSource; import okio.Okio; import org.junit.Test; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -256,7 +257,7 @@ public final class ResponseBodyTest { assertEquals(0xef, bytes[0] & 0xff); assertEquals(0xbb, bytes[1] & 0xff); assertEquals(0xbf, bytes[2] & 0xff); - assertEquals("hello", new String(bytes, 3, 5, "UTF-8")); + assertEquals("hello", new String(bytes, 3, 5, UTF_8)); } @Test public void bytesClosesUnderlyingSource() throws IOException { @@ -340,7 +341,7 @@ public final class ResponseBodyTest { assertEquals(0xef, bytes.read()); assertEquals(0xbb, bytes.read()); assertEquals(0xbf, bytes.read()); - assertEquals("hello", exhaust(new InputStreamReader(bytes, "utf-8"))); + assertEquals("hello", exhaust(new InputStreamReader(bytes, UTF_8))); } @Test public void byteStreamClosesUnderlyingSource() throws IOException { diff --git a/okhttp-tests/src/test/java/okhttp3/SocksProxy.java b/okhttp-tests/src/test/java/okhttp3/SocksProxy.java index d56241ac9112b2b61929fd0acc15f33e834ff07a..467517179817dc4f2c4926c5fb93180d9418d745 100644 --- a/okhttp-tests/src/test/java/okhttp3/SocksProxy.java +++ b/okhttp-tests/src/test/java/okhttp3/SocksProxy.java @@ -61,8 +61,7 @@ public final class SocksProxy { private ServerSocket serverSocket; private AtomicInteger connectionCount = new AtomicInteger(); - private final Set<Socket> openSockets = - Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>()); + private final Set<Socket> openSockets = Collections.newSetFromMap(new ConcurrentHashMap<>()); public void play() throws IOException { serverSocket = new ServerSocket(0); diff --git a/okhttp-tests/src/test/java/okhttp3/URLConnectionTest.java b/okhttp-tests/src/test/java/okhttp3/URLConnectionTest.java index 963f45f333c4b31e4c6c18812d6299f96ee7be7c..aad4bfa34b918cddf9eb6ba338e6cbfd00d6bfc3 100644 --- a/okhttp-tests/src/test/java/okhttp3/URLConnectionTest.java +++ b/okhttp-tests/src/test/java/okhttp3/URLConnectionTest.java @@ -87,11 +87,12 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Locale.US; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static okhttp3.TestUtil.defaultClient; -import static okhttp3.internal.Util.UTF_8; import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT; import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT; import static okhttp3.internal.huc.OkHttpURLConnection.SELECTED_PROTOCOL; @@ -325,7 +326,7 @@ public final class URLConnectionTest { connection = urlFactory.open(server.url("/def").url()); connection.setDoOutput(true); transferKind.setForRequest(connection, 4); - connection.getOutputStream().write("body".getBytes("UTF-8")); + connection.getOutputStream().write("body".getBytes(UTF_8)); assertContent("abc", connection); assertEquals("body", server.takeRequest().getBody().readUtf8()); @@ -342,7 +343,7 @@ public final class URLConnectionTest { connection.setDoOutput(true); connection.setChunkedStreamingMode(100); OutputStream os = connection.getOutputStream(); - os.write("OutputStream is no fun.".getBytes("UTF-8")); + os.write("OutputStream is no fun.".getBytes(UTF_8)); os.close(); try { @@ -469,12 +470,14 @@ public final class URLConnectionTest { assertContent("This comes after a busted connection", connection2); // Check that a fresh connection was created, either immediately or after attempting reuse. + // We know that a fresh connection was created if the server recorded a request with sequence + // number 0. Since the client may have attempted to reuse the broken connection just before + // creating a fresh connection, the server may have recorded 2 requests at this point. The order + // of recording is non-deterministic. RecordedRequest requestAfter = server.takeRequest(); - if (server.getRequestCount() == 3) { - requestAfter = server.takeRequest(); // The failure consumed a response. - } - // sequence number 0 means the HTTP socket connection was not reused - assertEquals(0, requestAfter.getSequenceNumber()); + assertTrue( + requestAfter.getSequenceNumber() == 0 + || server.getRequestCount() == 3 && server.takeRequest().getSequenceNumber() == 0); } enum WriteKind {BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS} @@ -907,11 +910,11 @@ public final class URLConnectionTest { @Test public void contentDisagreesWithChunkedHeaderBodyTooShort() throws IOException { MockResponse mockResponse = new MockResponse(); - mockResponse.setChunkedBody("abcde", 5); + mockResponse.setChunkedBody("abcdefg", 5); Buffer truncatedBody = new Buffer(); Buffer fullBody = mockResponse.getBody(); - truncatedBody.write(fullBody, fullBody.indexOf((byte) 'e')); + truncatedBody.write(fullBody, 4); mockResponse.setBody(truncatedBody); mockResponse.clearHeaders(); @@ -921,9 +924,9 @@ public final class URLConnectionTest { server.enqueue(mockResponse); try { - readAscii(urlFactory.open(server.url("/").url()).getInputStream(), 5); + readAscii(urlFactory.open(server.url("/").url()).getInputStream(), 7); fail(); - } catch (ProtocolException expected) { + } catch (IOException expected) { } } @@ -1538,7 +1541,7 @@ public final class URLConnectionTest { connection.setChunkedStreamingMode(0); // OkHttp does not honor specific chunk sizes. connection.setDoOutput(true); OutputStream outputStream = connection.getOutputStream(); - outputStream.write(body.getBytes("US-ASCII")); + outputStream.write(body.getBytes(US_ASCII)); assertEquals(200, connection.getResponseCode()); connection.getInputStream().close(); @@ -1619,7 +1622,7 @@ public final class URLConnectionTest { connection = urlFactory.open(server.url("/").url()); connection.setDoOutput(true); OutputStream outputStream = connection.getOutputStream(); - outputStream.write(body.getBytes("UTF-8")); + outputStream.write(body.getBytes(UTF_8)); outputStream.close(); assertEquals(200, connection.getResponseCode()); connection.getInputStream().close(); @@ -2302,7 +2305,7 @@ public final class URLConnectionTest { connection.addRequestProperty("Content-Type", "text/plain; charset=utf-8"); connection.addRequestProperty("Transfer-Encoding", "identity"); OutputStream outputStream = connection.getOutputStream(); - outputStream.write("ABCD".getBytes("UTF-8")); + outputStream.write("ABCD".getBytes(UTF_8)); outputStream.close(); assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); @@ -2487,7 +2490,7 @@ public final class URLConnectionTest { HttpURLConnection connection = urlFactory.open(server.url("/").url()); connection.setRequestMethod("POST"); - connection.getOutputStream().write("Hello".getBytes("UTF-8")); + connection.getOutputStream().write("Hello".getBytes(UTF_8)); assertEquals(200, connection.getResponseCode()); assertEquals("Body", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); @@ -2505,7 +2508,7 @@ public final class URLConnectionTest { HttpURLConnection connection = urlFactory.open(server.url("/").url()); connection.setRequestMethod("POST"); connection.setChunkedStreamingMode(0); - connection.getOutputStream().write("Hello".getBytes("UTF-8")); + connection.getOutputStream().write("Hello".getBytes(UTF_8)); assertEquals(408, connection.getResponseCode()); assertEquals(1, server.getRequestCount()); @@ -2583,7 +2586,7 @@ public final class URLConnectionTest { connection = urlFactory.open(server.url("/").url()); connection.setRequestProperty("Transfer-encoding", "chunked"); connection.setDoOutput(true); - connection.getOutputStream().write("ABC".getBytes("UTF-8")); + connection.getOutputStream().write("ABC".getBytes(UTF_8)); assertEquals(200, connection.getResponseCode()); RecordedRequest request = server.takeRequest(); @@ -2703,7 +2706,7 @@ public final class URLConnectionTest { connection = urlFactory.open(server.url("/").url()); connection.setDoOutput(true); - byte[] upload = "def".getBytes("UTF-8"); + byte[] upload = "def".getBytes(UTF_8); if (transferKind == TransferKind.CHUNKED) { connection.setChunkedStreamingMode(0); @@ -2717,7 +2720,7 @@ public final class URLConnectionTest { out.flush(); // Dubious but permitted. try { - out.write("ghi".getBytes("UTF-8")); + out.write("ghi".getBytes(UTF_8)); fail(); } catch (IOException expected) { } @@ -3527,11 +3530,7 @@ public final class URLConnectionTest { } @Test public void interceptorsNotInvoked() throws Exception { - Interceptor interceptor = new Interceptor() { - @Override public Response intercept(Chain chain) { - throw new AssertionError(); - } - }; + Interceptor interceptor = chain -> { throw new AssertionError(); }; urlFactory.setClient(urlFactory.client().newBuilder() .addInterceptor(interceptor) .addNetworkInterceptor(interceptor) @@ -3612,11 +3611,7 @@ public final class URLConnectionTest { /** Confirm that runtime exceptions thrown inside of OkHttp propagate to the caller. */ @Test public void unexpectedExceptionSync() throws Exception { urlFactory.setClient(urlFactory.client().newBuilder() - .dns(new Dns() { - @Override public List<InetAddress> lookup(String hostname) { - throw new RuntimeException("boom!"); - } - }) + .dns(hostname -> { throw new RuntimeException("boom!"); }) .build()); server.enqueue(new MockResponse()); @@ -3633,11 +3628,7 @@ public final class URLConnectionTest { /** Confirm that runtime exceptions thrown inside of OkHttp propagate to the caller. */ @Test public void unexpectedExceptionAsync() throws Exception { urlFactory.setClient(urlFactory.client().newBuilder() - .dns(new Dns() { - @Override public List<InetAddress> lookup(String hostname) { - throw new RuntimeException("boom!"); - } - }) + .dns(hostname -> { throw new RuntimeException("boom!"); }) .build()); server.enqueue(new MockResponse()); @@ -3752,7 +3743,7 @@ public final class URLConnectionTest { } enum TransferKind { - CHUNKED() { + CHUNKED { @Override void setBody(MockResponse response, Buffer content, int chunkSize) { response.setChunkedBody(content, chunkSize); } @@ -3761,7 +3752,7 @@ public final class URLConnectionTest { connection.setChunkedStreamingMode(5); } }, - FIXED_LENGTH() { + FIXED_LENGTH { @Override void setBody(MockResponse response, Buffer content, int chunkSize) { response.setBody(content); } @@ -3770,7 +3761,7 @@ public final class URLConnectionTest { connection.setFixedLengthStreamingMode(contentLength); } }, - END_OF_STREAM() { + END_OF_STREAM { @Override void setBody(MockResponse response, Buffer content, int chunkSize) { response.setBody(content); response.setSocketPolicy(DISCONNECT_AT_END); diff --git a/okhttp-tests/src/test/java/okhttp3/WebPlatformUrlTest.java b/okhttp-tests/src/test/java/okhttp3/WebPlatformUrlTest.java index 605d606fb077358b56e38ab3e24c203b957f307b..d9b3d442f396a467fb5aa64a20d200e5aa33b1c2 100644 --- a/okhttp-tests/src/test/java/okhttp3/WebPlatformUrlTest.java +++ b/okhttp-tests/src/test/java/okhttp3/WebPlatformUrlTest.java @@ -46,7 +46,7 @@ public final class WebPlatformUrlTest { } } - @Parameter(0) + @Parameter public WebPlatformUrlTestData testData; private static final List<String> HTTP_URL_SCHEMES @@ -58,8 +58,6 @@ public final class WebPlatformUrlTest { "Parsing: <http://f:\n/c> against <http://example.org/foo/bar>", "Parsing: <http://f:999999/c> against <http://example.org/foo/bar>", "Parsing: <http://192.0x00A80001> against <about:blank>", - // This test fails on Java 7 but passes on Java 8. See HttpUrlTest.hostWithTrailingDot(). - "Parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>", "Parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>", "Parsing: <http://192.168.0.257> against <http://other.com/>", "Parsing: <http://ï¼ï¼¸ï½ƒï¼ï¼Žï¼ï¼’5ï¼ï¼Žï¼ï¼‘> against <http://other.com/>" diff --git a/okhttp-tests/src/test/java/okhttp3/WholeOperationTimeoutTest.java b/okhttp-tests/src/test/java/okhttp3/WholeOperationTimeoutTest.java index 603f221397d9b3d816893cc73f33465305edd55b..09adba67f4bd12e68579ec8a93eb59c603d5a8ff 100644 --- a/okhttp-tests/src/test/java/okhttp3/WholeOperationTimeoutTest.java +++ b/okhttp-tests/src/test/java/okhttp3/WholeOperationTimeoutTest.java @@ -256,6 +256,29 @@ public final class WholeOperationTimeoutTest { } } + @Test + public void timeoutFollowingRedirectOnNewConnection() throws Exception { + MockWebServer otherServer = new MockWebServer(); + + server.enqueue( + new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) + .setHeader("Location", otherServer.url("/"))); + + otherServer.enqueue(new MockResponse().setHeadersDelay(500, TimeUnit.MILLISECONDS)); + + Request request = new Request.Builder().url(server.url("/")).build(); + + Call call = client.newCall(request); + call.timeout().timeout(250, TimeUnit.MILLISECONDS); + try { + Response response = call.execute(); + fail(); + } catch (IOException e) { + assertTrue(call.isCanceled()); + } + } + @Test public void noTimeout() throws Exception { server.enqueue(new MockResponse() .setHeadersDelay(250, TimeUnit.MILLISECONDS) diff --git a/okhttp-tests/src/test/java/okhttp3/internal/UtilTest.java b/okhttp-tests/src/test/java/okhttp3/internal/UtilTest.java index af92f5eb8409c3c013ba862f42cab30e4b5625a6..dacf8ddaffc5752fb3cf31205d76c8c17624c27b 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/UtilTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/UtilTest.java @@ -1,18 +1,18 @@ - /* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package okhttp3.internal; import java.util.Collections; @@ -21,17 +21,9 @@ import java.util.Map; import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; - public class UtilTest { - @Test public void testAssertionError() { - NullPointerException nullPointerException = new NullPointerException(); - AssertionError ae = Util.assertionError("npe", nullPointerException); - assertSame(nullPointerException, ae.getCause()); - assertEquals("npe", ae.getMessage()); - } - +public final class UtilTest { @Test public void immutableMap() { Map<String, String> map = new LinkedHashMap<>(); map.put("a", "A"); diff --git a/okhttp-tests/src/test/java/okhttp3/internal/cache/DiskLruCacheTest.java b/okhttp-tests/src/test/java/okhttp3/internal/cache/DiskLruCacheTest.java index 04ba57126bdf978b7b093c9ad769ac36782c4033..1d5bf3073127faa49640c3b8f9e73d6bc80c83ad 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/cache/DiskLruCacheTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/cache/DiskLruCacheTest.java @@ -33,7 +33,6 @@ import okio.Okio; import okio.Source; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -129,35 +128,35 @@ public final class DiskLruCacheTest { try { key = "has_space "; cache.edit(key); - fail("Exepcting an IllegalArgumentException as the key was invalid."); + fail("Expecting an IllegalArgumentException as the key was invalid."); } catch (IllegalArgumentException iae) { assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage()); } try { key = "has_CR\r"; cache.edit(key); - fail("Exepcting an IllegalArgumentException as the key was invalid."); + fail("Expecting an IllegalArgumentException as the key was invalid."); } catch (IllegalArgumentException iae) { assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage()); } try { key = "has_LF\n"; cache.edit(key); - fail("Exepcting an IllegalArgumentException as the key was invalid."); + fail("Expecting an IllegalArgumentException as the key was invalid."); } catch (IllegalArgumentException iae) { assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage()); } try { key = "has_invalid/"; cache.edit(key); - fail("Exepcting an IllegalArgumentException as the key was invalid."); + fail("Expecting an IllegalArgumentException as the key was invalid."); } catch (IllegalArgumentException iae) { assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage()); } try { key = "has_invalid\u2603"; cache.edit(key); - fail("Exepcting an IllegalArgumentException as the key was invalid."); + fail("Expecting an IllegalArgumentException as the key was invalid."); } catch (IllegalArgumentException iae) { assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage()); } @@ -165,7 +164,7 @@ public final class DiskLruCacheTest { key = "this_is_way_too_long_this_is_way_too_long_this_is_way_too_long_" + "this_is_way_too_long_this_is_way_too_long_this_is_way_too_long"; cache.edit(key); - fail("Exepcting an IllegalArgumentException as the key was too long."); + fail("Expecting an IllegalArgumentException as the key was too long."); } catch (IllegalArgumentException iae) { assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage()); } @@ -1249,7 +1248,7 @@ public final class DiskLruCacheTest { a.close(); iterator.remove(); - assertEquals(null, cache.get("a")); + assertNull(cache.get("a")); } @Test public void iteratorRemoveBeforeNext() throws Exception { diff --git a/okhttp-tests/src/test/java/okhttp3/internal/cache2/RelayTest.java b/okhttp-tests/src/test/java/okhttp3/internal/cache2/RelayTest.java index cbffa87d0992fe864d7715ccac05d8ebe393acbe..8ed0c911f79a6e2af5c18d6f10f9e9ad99146fbc 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/cache2/RelayTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/cache2/RelayTest.java @@ -227,14 +227,12 @@ public final class RelayTest { /** Returns a callable that reads all of source, closes it, and returns the bytes. */ private Callable<ByteString> sourceReader(final Source source) { - return new Callable<ByteString>() { - @Override public ByteString call() throws Exception { - Buffer buffer = new Buffer(); - while (source.read(buffer, 16384) != -1) { - } - source.close(); - return buffer.readByteString(); + return () -> { + Buffer buffer = new Buffer(); + while (source.read(buffer, 16384) != -1) { } + source.close(); + return buffer.readByteString(); }; } diff --git a/okhttp-tests/src/test/java/okhttp3/internal/connection/ConnectionSpecSelectorTest.java b/okhttp-tests/src/test/java/okhttp3/internal/connection/ConnectionSpecSelectorTest.java index e366e4081b258a4a775e34198b45bf3c1af4b311..25e6b86a4a240ad6751c8cbe156a9c0dca14dfed 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/connection/ConnectionSpecSelectorTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/connection/ConnectionSpecSelectorTest.java @@ -75,7 +75,8 @@ public class ConnectionSpecSelectorTest { public void retryableSSLHandshakeException() throws Exception { ConnectionSpecSelector connectionSpecSelector = createConnectionSpecSelector(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS); - SSLSocket socket = createSocketWithEnabledProtocols(TlsVersion.TLS_1_1, TlsVersion.TLS_1_0); + SSLSocket socket = createSocketWithEnabledProtocols( + TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0); connectionSpecSelector.configureSecureSocket(socket); boolean retry = connectionSpecSelector.connectionFailed(RETRYABLE_EXCEPTION); @@ -85,20 +86,21 @@ public class ConnectionSpecSelectorTest { @Test public void someFallbacksSupported() throws Exception { - ConnectionSpec sslV3 = - new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) - .tlsVersions(TlsVersion.SSL_3_0) - .build(); + ConnectionSpec sslV3 = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) + .tlsVersions(TlsVersion.SSL_3_0) + .build(); ConnectionSpecSelector connectionSpecSelector = createConnectionSpecSelector( ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS, sslV3); - TlsVersion[] enabledSocketTlsVersions = {TlsVersion.TLS_1_1, TlsVersion.TLS_1_0}; + TlsVersion[] enabledSocketTlsVersions = { + TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0 + }; SSLSocket socket = createSocketWithEnabledProtocols(enabledSocketTlsVersions); // MODERN_TLS is used here. connectionSpecSelector.configureSecureSocket(socket); - assertEnabledProtocols(socket, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0); + assertEnabledProtocols(socket, TlsVersion.TLS_1_2); boolean retry = connectionSpecSelector.connectionFailed(RETRYABLE_EXCEPTION); assertTrue(retry); @@ -107,7 +109,7 @@ public class ConnectionSpecSelectorTest { // COMPATIBLE_TLS is used here. socket = createSocketWithEnabledProtocols(enabledSocketTlsVersions); connectionSpecSelector.configureSecureSocket(socket); - assertEnabledProtocols(socket, TlsVersion.TLS_1_0); + assertEnabledProtocols(socket, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0); retry = connectionSpecSelector.connectionFailed(RETRYABLE_EXCEPTION); assertFalse(retry); diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http/DisconnectTest.java b/okhttp-tests/src/test/java/okhttp3/internal/http/DisconnectTest.java index aad2bfc8ddff07371c1051ecd11736e72f888fcf..0a89a1a56799d0db22bda1bea964cb1bff081678 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/http/DisconnectTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/http/DisconnectTest.java @@ -117,17 +117,15 @@ public final class DisconnectTest { responseBody.close(); } - private void disconnectLater(final HttpURLConnection connection, final int delayMillis) { - Thread interruptingCow = new Thread() { - @Override public void run() { - try { - sleep(delayMillis); - connection.disconnect(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + private void disconnectLater(HttpURLConnection connection, int delayMillis) { + Thread interruptingCow = new Thread(() -> { + try { + Thread.sleep(delayMillis); + connection.disconnect(); + } catch (InterruptedException e) { + throw new RuntimeException(e); } - }; + }); interruptingCow.start(); } } diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http/ThreadInterruptTest.java b/okhttp-tests/src/test/java/okhttp3/internal/http/ThreadInterruptTest.java index ed2b05085f71127161b1b6c8f59f3ba2ce39b353..8926e10c314d01ae7c5339a2a0deba2accea72a0 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/http/ThreadInterruptTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/http/ThreadInterruptTest.java @@ -123,18 +123,16 @@ public final class ThreadInterruptTest { responseBody.close(); } - private void interruptLater(final int delayMillis) { - final Thread toInterrupt = Thread.currentThread(); - Thread interruptingCow = new Thread() { - @Override public void run() { - try { - sleep(delayMillis); - toInterrupt.interrupt(); - } catch (InterruptedException e) { - throw new AssertionError(e); - } + private void interruptLater(int delayMillis) { + Thread toInterrupt = Thread.currentThread(); + Thread interruptingCow = new Thread(() -> { + try { + Thread.sleep(delayMillis); + toInterrupt.interrupt(); + } catch (InterruptedException e) { + throw new AssertionError(e); } - }; + }); interruptingCow.start(); } } diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http2/HpackTest.java b/okhttp-tests/src/test/java/okhttp3/internal/http2/HpackTest.java index 454f5eec997b35e0f736817a6208d3bd4ba6a1e1..f8bd931282c17d88867a1a32471ff9c472734825 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/http2/HpackTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/http2/HpackTest.java @@ -26,6 +26,7 @@ import org.junit.Test; import static okhttp3.TestUtil.headerEntries; import static okio.ByteString.decodeHex; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; public final class HpackTest { @@ -384,7 +385,7 @@ public final class HpackTest { assertEquals(0, hpackReader.headerCount); assertEquals(0, hpackReader.dynamicTableByteCount); - assertEquals(null, hpackReader.dynamicTable[readerHeaderTableLength() - 1]); + assertNull(hpackReader.dynamicTable[readerHeaderTableLength() - 1]); assertEquals(headerEntries(":method", "GET"), hpackReader.getAndResetHeaderList()); } diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2ConnectionTest.java b/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2ConnectionTest.java index ea5b8ed60b91fc234899d512323bdb1bf963a935..f457f8f82283928b1ce357a966426803f74671f8 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2ConnectionTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2ConnectionTest.java @@ -18,7 +18,6 @@ package okhttp3.internal.http2; import java.io.IOException; import java.io.InterruptedIOException; import java.net.Socket; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -37,14 +36,18 @@ import okio.Sink; import okio.Source; import okio.Utf8; import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; import org.junit.rules.Timeout; +import static java.nio.charset.StandardCharsets.UTF_8; import static okhttp3.TestUtil.headerEntries; import static okhttp3.TestUtil.repeat; +import static okhttp3.internal.Internal.initializeInstanceForTests; import static okhttp3.internal.Util.EMPTY_BYTE_ARRAY; +import static okhttp3.internal.Util.EMPTY_HEADERS; import static okhttp3.internal.http2.Http2Connection.Listener.REFUSE_INCOMING_STREAMS; import static okhttp3.internal.http2.Settings.DEFAULT_INITIAL_WINDOW_SIZE; import static okhttp3.internal.http2.Settings.ENABLE_PUSH; @@ -52,6 +55,7 @@ import static okhttp3.internal.http2.Settings.HEADER_TABLE_SIZE; import static okhttp3.internal.http2.Settings.INITIAL_WINDOW_SIZE; import static okhttp3.internal.http2.Settings.MAX_CONCURRENT_STREAMS; import static okhttp3.internal.http2.Settings.MAX_FRAME_SIZE; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -62,6 +66,10 @@ public final class Http2ConnectionTest { @Rule public final TestRule timeout = new Timeout(5_000); + @Before public void setup() { + initializeInstanceForTests(); + } + @After public void tearDown() throws Exception { peer.close(); } @@ -188,11 +196,11 @@ public final class Http2ConnectionTest { InFrame data1 = peer.takeFrame(); assertEquals(Http2.TYPE_DATA, data1.type); assertEquals(3, data1.streamId); - assertTrue(Arrays.equals("abcde".getBytes("UTF-8"), data1.data)); + assertArrayEquals("abcde".getBytes(UTF_8), data1.data); InFrame data2 = peer.takeFrame(); assertEquals(Http2.TYPE_DATA, data2.type); assertEquals(3, data2.streamId); - assertTrue(Arrays.equals("fghi".getBytes("UTF-8"), data2.data)); + assertArrayEquals("fghi".getBytes(UTF_8), data2.data); } /** @@ -204,7 +212,7 @@ public final class Http2ConnectionTest { peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM 3 - peer.sendFrame().headers(3, headerEntries("a", "apple")); + peer.sendFrame().headers(false, 3, headerEntries("a", "apple")); peer.sendFrame().data(false, 3, data(1024), 1024); peer.acceptFrame(); // RST_STREAM peer.sendFrame().data(true, 3, data(1024), 1024); @@ -276,7 +284,7 @@ public final class Http2ConnectionTest { InFrame data1 = peer.takeFrame(); assertEquals(Http2.TYPE_DATA, data1.type); assertEquals(3, data1.streamId); - assertTrue(Arrays.equals("abcdef".getBytes("UTF-8"), data1.data)); + assertArrayEquals("abcdef".getBytes(UTF_8), data1.data); } @Test public void readSendsWindowUpdateHttp2() throws Exception { @@ -287,7 +295,7 @@ public final class Http2ConnectionTest { peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); for (int i = 0; i < 3; i++) { // Send frames of summing to size 50, which is windowUpdateThreshold. peer.sendFrame().data(false, 3, data(24), 24); @@ -331,7 +339,7 @@ public final class Http2ConnectionTest { peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); peer.sendFrame().data(true, 3, data(0), 0); peer.play(); @@ -352,7 +360,7 @@ public final class Http2ConnectionTest { peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM peer.acceptFrame(); // DATA - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); peer.play(); // Play it back. @@ -377,7 +385,7 @@ public final class Http2ConnectionTest { peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); peer.acceptFrame(); // DATA peer.acceptFrame(); // DATA peer.play(); @@ -403,7 +411,7 @@ public final class Http2ConnectionTest { peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); final List<Header> expectedRequestHeaders = Arrays.asList( new Header(Header.TARGET_METHOD, "GET"), new Header(Header.TARGET_SCHEME, "https"), @@ -414,7 +422,7 @@ public final class Http2ConnectionTest { final List<Header> expectedResponseHeaders = Arrays.asList( new Header(Header.RESPONSE_STATUS, "200") ); - peer.sendFrame().synReply(true, 2, expectedResponseHeaders); + peer.sendFrame().headers(true, 2, expectedResponseHeaders); peer.sendFrame().data(true, 3, data(0), 0); peer.play(); @@ -461,7 +469,7 @@ public final class Http2ConnectionTest { new Header(Header.TARGET_AUTHORITY, "squareup.com"), new Header(Header.TARGET_PATH, "/cached") )); - peer.sendFrame().synReply(true, 2, Arrays.asList( + peer.sendFrame().headers(true, 2, Arrays.asList( new Header(Header.RESPONSE_STATUS, "200") )); peer.acceptFrame(); // RST_STREAM @@ -514,7 +522,7 @@ public final class Http2ConnectionTest { peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM peer.acceptFrame(); // DATA - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); peer.sendFrame().data(true, 3, new Buffer().writeUtf8("robot"), 5); peer.acceptFrame(); // PING peer.sendFrame().ping(true, 1, 0); // PING @@ -539,106 +547,193 @@ public final class Http2ConnectionTest { assertEquals(-1, synStream.associatedStreamId); assertEquals(headerEntries("b", "banana"), synStream.headerBlock); InFrame requestData = peer.takeFrame(); - assertTrue(Arrays.equals("c3po".getBytes("UTF-8"), requestData.data)); + assertArrayEquals("c3po".getBytes(UTF_8), requestData.data); } - static final class RecordingHeadersListener implements Header.Listener { - final ArrayDeque<Headers> receivedHeaders = new ArrayDeque<>(); + @Test public void serverFinishesStreamWithHeaders() throws Exception { + // write the mocking script + peer.sendFrame().settings(new Settings()); + peer.acceptFrame(); // ACK + peer.acceptFrame(); // SYN_STREAM + peer.acceptFrame(); // PING + peer.sendFrame().headers(true, 3, headerEntries("headers", "bam")); + peer.sendFrame().ping(true, 1, 0); // PONG + peer.play(); - @Override public void onHeaders(Headers headers) { - receivedHeaders.add(headers); - } + // play it back + Http2Connection connection = connect(peer); + Http2Stream stream = connection.newStream(headerEntries("a", "artichaut"), false); + connection.writePingAndAwaitPong(); + assertEquals(Headers.of("headers", "bam"), stream.takeHeaders()); + assertEquals(EMPTY_HEADERS, stream.trailers()); + assertEquals(0, connection.openStreamCount()); - public List<Headers> takeAll() { - List<Headers> result = new ArrayList<>(); - for (Headers headers; (headers = receivedHeaders.poll()) != null; ) { - result.add(headers); - } - return result; - } + // verify the peer received what was expected + InFrame synStream = peer.takeFrame(); + assertEquals(Http2.TYPE_HEADERS, synStream.type); + assertFalse(synStream.outFinished); + assertEquals(3, synStream.streamId); + assertEquals(-1, synStream.associatedStreamId); + assertEquals(headerEntries("a", "artichaut"), synStream.headerBlock); } - @Test public void clientReadsHeadersDataHeadersData() throws Exception { + @Test public void serverWritesTrailersAndClientReadsTrailers() throws Exception { + // write the mocking script peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); - peer.sendFrame().data(false, 3, new Buffer().writeUtf8("robot"), 5); - peer.sendFrame().synReply(false, 3, headerEntries("b", "banana")); - peer.sendFrame().data(true, 3, new Buffer().writeUtf8("cyborg"), 6); + peer.sendFrame().headers(false, 3, headerEntries("headers", "bam")); peer.acceptFrame(); // PING + peer.sendFrame().headers(true, 3, headerEntries("trailers", "boom")); peer.sendFrame().ping(true, 1, 0); // PONG peer.play(); - RecordingHeadersListener headersListener = new RecordingHeadersListener(); - + // play it back Http2Connection connection = connect(peer); - Http2Stream stream = connection.newStream(headerEntries(), false); - stream.setHeadersListener(headersListener); - assertStreamData("robotcyborg", stream.getSource()); - assertEquals(Arrays.asList(Headers.of("a", "android"), Headers.of("b", "banana")), - headersListener.takeAll()); + Http2Stream stream = connection.newStream(headerEntries("a", "artichaut"), false); + assertEquals(Headers.of("headers", "bam"), stream.takeHeaders()); connection.writePingAndAwaitPong(); + assertEquals(Headers.of("trailers", "boom"), stream.trailers()); assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + InFrame synStream = peer.takeFrame(); + assertEquals(Http2.TYPE_HEADERS, synStream.type); + assertFalse(synStream.outFinished); + assertEquals(3, synStream.streamId); + assertEquals(-1, synStream.associatedStreamId); + assertEquals(headerEntries("a", "artichaut"), synStream.headerBlock); + } + + @Test public void serverWritesTrailersWithData() throws Exception { + // We buffer some outbound data and headers and confirm that the END_STREAM flag comes with the + // headers (and not with the data). + + // write the mocking script for the client + peer.setClient(true); + + // Write the mocking script. + peer.sendFrame().settings(new Settings()); + peer.sendFrame().headers(true, 3, headerEntries("client", "abc")); + peer.acceptFrame(); // ACK + peer.acceptFrame(); // HEADERS STREAM 3 + peer.acceptFrame(); // DATA STREAM 3 "abcde" + peer.acceptFrame(); // HEADERS STREAM 3 + peer.play(); + + // Play it back. + Http2Connection connection = connect(peer); + Http2Stream stream = connection.newStream(headerEntries("a", "android"), true); + stream.enqueueTrailers(Headers.of("foo", "bar")); + BufferedSink sink = Okio.buffer(stream.getSink()); + sink.writeUtf8("abcdefghi"); + sink.close(); + + // Verify the peer received what was expected. + InFrame headers1 = peer.takeFrame(); + assertEquals(Http2.TYPE_HEADERS, headers1.type); + InFrame data1 = peer.takeFrame(); + assertEquals(Http2.TYPE_DATA, data1.type); + assertEquals(3, data1.streamId); + assertArrayEquals("abcdefghi".getBytes(UTF_8), data1.data); + assertFalse(data1.inFinished); + InFrame headers2 = peer.takeFrame(); + assertEquals(Http2.TYPE_HEADERS, headers2.type); + assertTrue(headers2.inFinished); } - @Test public void clientReadsHeadersDataPingPongHeadersData() throws Exception { + @Test public void clientCannotReadTrailersWithoutExhaustingStream() throws Exception { + // write the mocking script peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); peer.sendFrame().data(false, 3, new Buffer().writeUtf8("robot"), 5); + peer.sendFrame().headers(true, 3, headerEntries("trailers", "boom")); peer.acceptFrame(); // PING peer.sendFrame().ping(true, 1, 0); // PONG - peer.sendFrame().synReply(false, 3, headerEntries("b", "banana")); - peer.sendFrame().data(true, 3, new Buffer().writeUtf8("cyborg"), 6); + peer.play(); + + // play it back + Http2Connection connection = connect(peer); + Http2Stream stream = connection.newStream(headerEntries("a", "artichaut"), true); + connection.writePingAndAwaitPong(); + try { + stream.trailers(); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void clientCannotReadTrailersIfTheStreamFailed() throws Exception { + // write the mocking script + peer.sendFrame().settings(new Settings()); + peer.acceptFrame(); // ACK + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().rstStream(3, ErrorCode.PROTOCOL_ERROR); peer.acceptFrame(); // PING peer.sendFrame().ping(true, 1, 0); // PONG peer.play(); - RecordingHeadersListener headersListener = new RecordingHeadersListener(); - + // play it back Http2Connection connection = connect(peer); - Http2Stream stream = connection.newStream(headerEntries(), false); - stream.setHeadersListener(headersListener); - BufferedSource source = Okio.buffer(stream.getSource()); - - assertStreamPrefix("robot", source); - assertEquals(Arrays.asList(Headers.of("a", "android")), headersListener.takeAll()); + Http2Stream stream = connection.newStream(headerEntries("a", "artichaut"), true); connection.writePingAndAwaitPong(); + try { + stream.trailers(); + fail(); + } catch (StreamResetException expected) { + } + } - assertStreamPrefix("cyborg", source); - assertEquals(Arrays.asList(Headers.of("b", "banana")), headersListener.takeAll()); - connection.writePingAndAwaitPong(); + @Test public void serverCannotEnqueueTrailersAfterFinishingTheStream() throws Exception { + peer.setClient(true); + peer.sendFrame().settings(new Settings()); + peer.acceptFrame(); // ACK + peer.acceptFrame(); // PING + peer.sendFrame().ping(true, 1, 0); + peer.play(); - assertEquals(0, connection.openStreamCount()); + // Play it back. + Http2Connection connection = connect(peer); + connection.writePingAndAwaitPong(); + Http2Stream stream = connection.newStream(headerEntries("a", "android"), true); + // finish the stream + stream.writeHeaders(headerEntries("b", "berserk"), true, false); + try { + stream.enqueueTrailers(Headers.of("trailers", "boom")); + fail(); + } catch (IllegalStateException expected) { + } } - @Test public void clientReadsHeadersDataHeadersClose() throws Exception { + @Test public void noTrailersFrameYieldsEmptyTrailers() throws Exception { + // write the mocking script peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); - peer.sendFrame().data(false, 3, new Buffer().writeUtf8("robot"), 5); + peer.sendFrame().headers(false, 3, headerEntries("headers", "bam")); + peer.sendFrame().data(true, 3, new Buffer().writeUtf8("robot"), 5); peer.acceptFrame(); // PING - peer.sendFrame().synReply(false, 3, headerEntries("b", "banana")); peer.sendFrame().ping(true, 1, 0); // PONG peer.play(); - RecordingHeadersListener headersListener = new RecordingHeadersListener(); - + // play it back Http2Connection connection = connect(peer); - Http2Stream stream = connection.newStream(headerEntries(), false); - stream.setHeadersListener(headersListener); + Http2Stream stream = connection.newStream(headerEntries("a", "artichaut"), false); BufferedSource source = Okio.buffer(stream.getSource()); - - assertStreamPrefix("robot", source); - assertEquals(Arrays.asList(Headers.of("a", "android")), headersListener.takeAll()); connection.writePingAndAwaitPong(); - source.close(); - assertEquals(Arrays.asList(Headers.of("b", "banana")), headersListener.takeAll()); - + assertEquals(Headers.of("headers", "bam"), stream.takeHeaders()); + assertEquals("robot", source.readUtf8(5)); + assertEquals(EMPTY_HEADERS, stream.trailers()); assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + InFrame synStream = peer.takeFrame(); + assertEquals(Http2.TYPE_HEADERS, synStream.type); + assertFalse(synStream.outFinished); + assertEquals(3, synStream.streamId); + assertEquals(-1, synStream.associatedStreamId); + assertEquals(headerEntries("a", "artichaut"), synStream.headerBlock); } @Test public void serverReadsHeadersDataHeaders() throws Exception { @@ -648,7 +743,7 @@ public final class Http2ConnectionTest { peer.acceptFrame(); // SYN_STREAM peer.acceptFrame(); // DATA peer.acceptFrame(); // HEADERS - peer.sendFrame().synReply(true, 3, headerEntries("a", "android")); + peer.sendFrame().headers(true, 3, headerEntries("a", "android")); peer.acceptFrame(); // PING peer.sendFrame().ping(true, 1, 0); // PING peer.play(); @@ -659,7 +754,7 @@ public final class Http2ConnectionTest { BufferedSink out = Okio.buffer(stream.getSink()); out.writeUtf8("c3po"); out.close(); - stream.writeHeaders(headerEntries("e", "elephant"), true); + stream.writeHeaders(headerEntries("e", "elephant"), false, false); connection.writePingAndAwaitPong(); assertEquals(0, connection.openStreamCount()); @@ -671,7 +766,7 @@ public final class Http2ConnectionTest { assertEquals(-1, synStream.associatedStreamId); assertEquals(headerEntries("b", "banana"), synStream.headerBlock); InFrame requestData = peer.takeFrame(); - assertTrue(Arrays.equals("c3po".getBytes("UTF-8"), requestData.data)); + assertArrayEquals("c3po".getBytes(UTF_8), requestData.data); InFrame nextFrame = peer.takeFrame(); assertEquals(headerEntries("e", "elephant"), nextFrame.headerBlock); @@ -683,7 +778,7 @@ public final class Http2ConnectionTest { peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM peer.acceptFrame(); // PING - peer.sendFrame().synReply(true, 3, headerEntries("a", "android")); + peer.sendFrame().headers(true, 3, headerEntries("a", "android")); peer.sendFrame().ping(true, 1, 0); peer.play(); @@ -883,7 +978,7 @@ public final class Http2ConnectionTest { // write the mocking script peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK - peer.sendFrame().synReply(false, 41, headerEntries("a", "android")); + peer.sendFrame().headers(false, 41, headerEntries("a", "android")); peer.sendFrame().ping(false, 2, 0); peer.acceptFrame(); // PING peer.play(); @@ -1014,7 +1109,7 @@ public final class Http2ConnectionTest { assertFalse(synStream.outFinished); InFrame data = peer.takeFrame(); assertEquals(Http2.TYPE_DATA, data.type); - assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data)); + assertArrayEquals("square".getBytes(UTF_8), data.data); InFrame fin = peer.takeFrame(); assertEquals(Http2.TYPE_DATA, fin.type); assertTrue(fin.inFinished); @@ -1029,7 +1124,7 @@ public final class Http2ConnectionTest { peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("b", "banana")); + peer.sendFrame().headers(false, 3, headerEntries("b", "banana")); peer.sendFrame().data(true, 3, new Buffer().writeUtf8("square"), 6); peer.acceptFrame(); // PING peer.sendFrame().ping(true, 1, 0); @@ -1055,9 +1150,9 @@ public final class Http2ConnectionTest { peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); peer.acceptFrame(); // PING - peer.sendFrame().synReply(false, 3, headerEntries("b", "banana")); + peer.sendFrame().headers(false, 3, headerEntries("b", "banana")); peer.sendFrame().ping(true, 1, 0); peer.play(); @@ -1079,7 +1174,7 @@ public final class Http2ConnectionTest { peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); peer.sendFrame().data(true, 3, new Buffer().writeUtf8("robot"), 5); peer.sendFrame().data(true, 3, new Buffer().writeUtf8("c3po"), 4); peer.acceptFrame(); // RST_STREAM @@ -1110,7 +1205,7 @@ public final class Http2ConnectionTest { peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("b", "banana")); + peer.sendFrame().headers(false, 3, headerEntries("b", "banana")); peer.sendFrame().data(false, 3, new Buffer().write(new byte[dataLength]), dataLength); peer.sendFrame().data(false, 3, new Buffer().write(new byte[dataLength]), dataLength); peer.sendFrame().data(false, 3, new Buffer().write(new byte[dataLength]), dataLength); @@ -1210,7 +1305,7 @@ public final class Http2ConnectionTest { InFrame data1 = peer.takeFrame(); assertEquals(Http2.TYPE_DATA, data1.type); assertEquals(3, data1.streamId); - assertTrue(Arrays.equals("abcdef".getBytes("UTF-8"), data1.data)); + assertArrayEquals("abcdef".getBytes(UTF_8), data1.data); } @Test public void sendGoAway() throws Exception { @@ -1220,7 +1315,7 @@ public final class Http2ConnectionTest { peer.acceptFrame(); // SYN_STREAM 1 peer.acceptFrame(); // GOAWAY peer.acceptFrame(); // PING - peer.sendFrame().synStream(false, 2, 0, headerEntries("b", "b")); // Should be ignored! + peer.sendFrame().headers(false, 2, headerEntries("b", "b")); // Should be ignored! peer.sendFrame().ping(true, 1, 0); peer.play(); @@ -1326,7 +1421,7 @@ public final class Http2ConnectionTest { peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); peer.acceptFrame(); // RST_STREAM peer.play(); @@ -1361,7 +1456,7 @@ public final class Http2ConnectionTest { peer.acceptFrame(); // PING peer.sendFrame().ping(true, 1, 0); peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); peer.acceptFrame(); // DATA peer.acceptFrame(); // RST_STREAM peer.play(); @@ -1403,7 +1498,7 @@ public final class Http2ConnectionTest { peer.acceptFrame(); // PING peer.sendFrame().ping(true, 1, 0); peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); peer.acceptFrame(); // PING peer.sendFrame().ping(true, 3, 0); peer.acceptFrame(); // DATA @@ -1442,7 +1537,7 @@ public final class Http2ConnectionTest { peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); peer.acceptFrame(); // DATA peer.play(); @@ -1460,7 +1555,7 @@ public final class Http2ConnectionTest { assertEquals(Http2.TYPE_HEADERS, peer.takeFrame().type); InFrame data = peer.takeFrame(); assertEquals(Http2.TYPE_DATA, data.type); - assertTrue(Arrays.equals("abcdefghij".getBytes("UTF-8"), data.data)); + assertArrayEquals("abcdefghij".getBytes(UTF_8), data.data); assertTrue(data.inFinished); } @@ -1470,8 +1565,8 @@ public final class Http2ConnectionTest { peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM peer.acceptFrame(); // PING - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); - peer.sendFrame().headers(3, headerEntries("c", "c3po")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("c", "c3po")); peer.sendFrame().ping(true, 1, 0); peer.play(); @@ -1494,10 +1589,10 @@ public final class Http2ConnectionTest { peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); peer.acceptFrame(); // PING - peer.sendFrame().ping(true, 1, 0); // PING - peer.sendFrame().synReply(true, 3, headerEntries("c", "cola")); + peer.sendFrame().headers(true, 3, headerEntries("c", "cola")); + peer.sendFrame().ping(true, 1, 0); // PONG peer.play(); // play it back @@ -1506,7 +1601,7 @@ public final class Http2ConnectionTest { stream.getConnection().flush(); assertEquals(Headers.of("a", "android"), stream.takeHeaders()); connection.writePingAndAwaitPong(); - assertEquals(Headers.of("c", "cola"), stream.takeHeaders()); + assertEquals(Headers.of("c", "cola"), stream.trailers()); // verify the peer received what was expected assertEquals(Http2.TYPE_HEADERS, peer.takeFrame().type); @@ -1521,7 +1616,7 @@ public final class Http2ConnectionTest { peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); for (int i = 0; i < 3; i++) { // Send frames of summing to size 50, which is windowUpdateThreshold. peer.sendFrame().data(false, 3, data(24), 24); @@ -1565,7 +1660,7 @@ public final class Http2ConnectionTest { peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); peer.sendFrame().data(true, 3, data(0), 0); peer.play(); @@ -1586,7 +1681,7 @@ public final class Http2ConnectionTest { peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM peer.acceptFrame(); // DATA - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); peer.play(); // Play it back. @@ -1608,7 +1703,7 @@ public final class Http2ConnectionTest { peer.sendFrame().settings(new Settings()); peer.acceptFrame(); // ACK peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); peer.sendFrame().data(false, 3, data(1024), 1024); peer.truncateLastFrame(8 + 100); peer.play(); @@ -1671,7 +1766,7 @@ public final class Http2ConnectionTest { @Test public void remoteOmitsInitialSettings() throws Exception { // Write the mocking script. Note no SETTINGS frame is sent or acknowledged. peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().headers(false, 3, headerEntries("a", "android")); peer.acceptFrame(); // GOAWAY peer.play(); diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2Test.java b/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2Test.java index 24ec759c2d9261123ca934b37fd71a0053cafb99..684d2226933a6c6c387d92682886e38c4266109a 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2Test.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2Test.java @@ -240,7 +240,7 @@ public final class Http2Test { @Override public void settings(boolean clearPrevious, Settings settings) { assertFalse(clearPrevious); // No clearPrevious in HTTP/2. assertEquals(reducedTableSizeBytes, settings.getHeaderTableSize()); - assertEquals(false, settings.getEnablePush(true)); + assertFalse(settings.getEnablePush(true)); } }); } diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http2/HttpOverHttp2Test.java b/okhttp-tests/src/test/java/okhttp3/internal/http2/HttpOverHttp2Test.java index eb0acf496313d9bf1c572477632de3174322ba40..6a8bcd0be49da0a1e05a0994c7c7b10f3e8e9bf7 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/http2/HttpOverHttp2Test.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/http2/HttpOverHttp2Test.java @@ -46,7 +46,6 @@ import okhttp3.RecordingHostnameVerifier; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; -import okhttp3.Route; import okhttp3.TestLogHandler; import okhttp3.TestUtil; import okhttp3.internal.DoubleInetAddressDns; @@ -75,6 +74,7 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static okhttp3.TestUtil.defaultClient; @@ -149,7 +149,9 @@ public final class HttpOverHttp2Test { http2Logger.removeHandler(http2Handler); http2Logger.setLevel(previousLevel); + // Ensure a fresh connection pool for the next test. client.connectionPool().evictAll(); + assertEquals(0, client.connectionPool().connectionCount()); } @Test public void get() throws Exception { @@ -186,7 +188,7 @@ public final class HttpOverHttp2Test { } @Test public void noDefaultContentLengthOnStreamingPost() throws Exception { - final byte[] postBytes = "FGHIJ".getBytes(Util.UTF_8); + byte[] postBytes = "FGHIJ".getBytes(UTF_8); server.enqueue(new MockResponse().setBody("ABCDE")); @@ -213,7 +215,7 @@ public final class HttpOverHttp2Test { } @Test public void userSuppliedContentLengthHeader() throws Exception { - final byte[] postBytes = "FGHIJ".getBytes(Util.UTF_8); + byte[] postBytes = "FGHIJ".getBytes(UTF_8); server.enqueue(new MockResponse().setBody("ABCDE")); @@ -244,7 +246,7 @@ public final class HttpOverHttp2Test { } @Test public void closeAfterFlush() throws Exception { - final byte[] postBytes = "FGHIJ".getBytes(Util.UTF_8); + byte[] postBytes = "FGHIJ".getBytes(UTF_8); server.enqueue(new MockResponse().setBody("ABCDE")); @@ -352,7 +354,7 @@ public final class HttpOverHttp2Test { waitForDataFrames(Http2Connection.OKHTTP_CLIENT_WINDOW_SIZE); // Cancel the call and close the response body. This should discard the buffered data and update - // the connnection flow-control window. + // the connection flow-control window. call1.cancel(); response1.close(); @@ -752,13 +754,7 @@ public final class HttpOverHttp2Test { cookieJar.assertResponseCookies("a=b; path=/"); } - /** https://github.com/square/okhttp/issues/1191 */ - @Ignore // TODO: recover gracefully when a connection is shutdown. @Test public void cancelWithStreamNotCompleted() throws Exception { - // Ensure that the (shared) connection pool is in a consistent state. - client.connectionPool().evictAll(); - assertEquals(0, client.connectionPool().connectionCount()); - server.enqueue(new MockResponse() .setBody("abc")); server.enqueue(new MockResponse() @@ -900,8 +896,10 @@ public final class HttpOverHttp2Test { Call call = client.newCall(new Request.Builder() .url(server.url("/")) .build()); + CountDownLatch latch = new CountDownLatch(1); call.enqueue(new Callback() { @Override public void onFailure(Call call1, IOException e) { + latch.countDown(); } @Override public void onResponse(Call call1, Response response) { @@ -909,6 +907,7 @@ public final class HttpOverHttp2Test { }); assertEquals(expectedSequenceNumber, server.takeRequest().getSequenceNumber()); call.cancel(); + latch.await(); } @Test public void noRecoveryFromRefusedStreamWithRetryDisabled() throws Exception { @@ -955,18 +954,16 @@ public final class HttpOverHttp2Test { server.enqueue(new MockResponse() .setBody("ABC")); - final CountDownLatch latch = new CountDownLatch(1); - final BlockingQueue<String> responses = new SynchronousQueue<>(); - okhttp3.Authenticator authenticator = new okhttp3.Authenticator() { - @Override public Request authenticate(Route route, Response response) throws IOException { - responses.offer(response.body().string()); - try { - latch.await(); - } catch (InterruptedException e) { - throw new AssertionError(); - } - return response.request(); + CountDownLatch latch = new CountDownLatch(1); + BlockingQueue<String> responses = new SynchronousQueue<>(); + okhttp3.Authenticator authenticator = (route, response) -> { + responses.offer(response.body().string()); + try { + latch.await(); + } catch (InterruptedException e) { + throw new AssertionError(); } + return response.request(); }; OkHttpClient blockingAuthClient = client.newBuilder() @@ -1314,7 +1311,7 @@ public final class HttpOverHttp2Test { .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END) .setBody("DEF")); - final BlockingQueue<String> bodies = new SynchronousQueue<>(); + BlockingQueue<String> bodies = new SynchronousQueue<>(); Callback callback = new Callback() { @Override public void onResponse(Call call, Response response) throws IOException { bodies.add(response.body().string()); @@ -1343,10 +1340,7 @@ public final class HttpOverHttp2Test { server.useHttps(handshakeCertificates.sslSocketFactory(), true); - // Force a fresh connection pool for the test. - client.connectionPool().evictAll(); - - final QueueDispatcher queueDispatcher = new QueueDispatcher(); + QueueDispatcher queueDispatcher = new QueueDispatcher(); queueDispatcher.enqueueResponse(new MockResponse() .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) .clearHeaders()); diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http2/HuffmanTest.java b/okhttp-tests/src/test/java/okhttp3/internal/http2/HuffmanTest.java index 53642c0ea08828ae4d7c011897870870fb851d46..e92445f266043c4524e05cb1fb66619bbe3776b1 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/http2/HuffmanTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/http2/HuffmanTest.java @@ -16,14 +16,13 @@ package okhttp3.internal.http2; import java.io.IOException; -import java.util.Arrays; import java.util.Random; import okio.Buffer; import okio.ByteString; import org.junit.Test; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; /** Original version of this class was lifted from {@code com.twitter.hpack.HuffmanTest}. */ public final class HuffmanTest { @@ -45,6 +44,6 @@ public final class HuffmanTest { assertEquals(buffer.size(), Huffman.get().encodedLength(data)); byte[] decodedBytes = Huffman.get().decode(buffer.readByteArray()); - assertTrue(Arrays.equals(data.toByteArray(), decodedBytes)); + assertArrayEquals(data.toByteArray(), decodedBytes); } } diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http2/MockHttp2Peer.java b/okhttp-tests/src/test/java/okhttp3/internal/http2/MockHttp2Peer.java index 376c46d1f355e930c6e12e309767188a289a2283..1ade5b1dded99c0129e30dd430b3ce88bda20be1 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/http2/MockHttp2Peer.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/http2/MockHttp2Peer.java @@ -110,14 +110,12 @@ public final class MockHttp2Peer implements Closeable { serverSocket.setReuseAddress(false); serverSocket.bind(new InetSocketAddress("localhost", 0), 1); port = serverSocket.getLocalPort(); - executor.execute(new Runnable() { - @Override public void run() { - try { - readAndWriteFrames(); - } catch (IOException e) { - Util.closeQuietly(MockHttp2Peer.this); - logger.info(MockHttp2Peer.this + " done: " + e.getMessage()); - } + executor.execute(() -> { + try { + readAndWriteFrames(); + } catch (IOException e) { + Util.closeQuietly(MockHttp2Peer.this); + logger.info(MockHttp2Peer.this + " done: " + e.getMessage()); } }); } diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http2/SettingsTest.java b/okhttp-tests/src/test/java/okhttp3/internal/http2/SettingsTest.java index a26506ca273ad22da313d0f49aef8612fb955b76..46ec0a82ab8abe1733046456c60e067931afd40b 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/http2/SettingsTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/http2/SettingsTest.java @@ -20,6 +20,7 @@ import org.junit.Test; import static okhttp3.internal.http2.Settings.DEFAULT_INITIAL_WINDOW_SIZE; import static okhttp3.internal.http2.Settings.MAX_CONCURRENT_STREAMS; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public final class SettingsTest { @Test public void unsetField() { @@ -33,9 +34,9 @@ public final class SettingsTest { settings.set(Settings.HEADER_TABLE_SIZE, 8096); assertEquals(8096, settings.getHeaderTableSize()); - assertEquals(true, settings.getEnablePush(true)); + assertTrue(settings.getEnablePush(true)); settings.set(Settings.ENABLE_PUSH, 1); - assertEquals(true, settings.getEnablePush(false)); + assertTrue(settings.getEnablePush(false)); settings.clear(); assertEquals(-3, settings.getMaxConcurrentStreams(-3)); diff --git a/okhttp-tests/src/test/java/okhttp3/internal/platform/JdkWithJettyBootPlatformTest.java b/okhttp-tests/src/test/java/okhttp3/internal/platform/Jdk8WithJettyBootPlatformTest.java similarity index 89% rename from okhttp-tests/src/test/java/okhttp3/internal/platform/JdkWithJettyBootPlatformTest.java rename to okhttp-tests/src/test/java/okhttp3/internal/platform/Jdk8WithJettyBootPlatformTest.java index afe7145a01f8830bfdaf34cee7641cf36f63222c..bc981e9a364adfafa80c97424c551dcff4f18bb6 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/platform/JdkWithJettyBootPlatformTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/platform/Jdk8WithJettyBootPlatformTest.java @@ -21,11 +21,11 @@ import static okhttp3.internal.platform.PlatformTest.getPlatform; import static org.junit.Assert.assertNotNull; import static org.junit.Assume.assumeTrue; -public class JdkWithJettyBootPlatformTest { +public class Jdk8WithJettyBootPlatformTest { @Test public void testBuildsWithJettyBoot() { assumeTrue(getPlatform().equals("jdk-with-jetty-boot")); - assertNotNull(JdkWithJettyBootPlatform.buildIfSupported()); + assertNotNull(Jdk8WithJettyBootPlatform.buildIfSupported()); } } diff --git a/okhttp-tests/src/test/java/okhttp3/internal/platform/OptionalMethodTest.java b/okhttp-tests/src/test/java/okhttp3/internal/platform/OptionalMethodTest.java deleted file mode 100644 index 7e249bd61f4d9f46db1ac41b1fed34ebb5cecbc8..0000000000000000000000000000000000000000 --- a/okhttp-tests/src/test/java/okhttp3/internal/platform/OptionalMethodTest.java +++ /dev/null @@ -1,347 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package okhttp3.internal.platform; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import org.junit.Ignore; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -/** - * Tests for {@link OptionalMethod}. - */ -public class OptionalMethodTest { - @SuppressWarnings("unused") - private static class BaseClass { - public String stringMethod() { - return "string"; - } - - public void voidMethod() { - } - } - - @SuppressWarnings("unused") - private static class SubClass1 extends BaseClass { - public String subclassMethod() { - return "subclassMethod1"; - } - - public String methodWithArgs(String arg) { - return arg; - } - } - - @SuppressWarnings("unused") - private static class SubClass2 extends BaseClass { - public int subclassMethod() { - return 1234; - } - - public String methodWithArgs(String arg) { - return arg; - } - - public void throwsException() throws IOException { - throw new IOException(); - } - - public void throwsRuntimeException() throws Exception { - throw new NumberFormatException(); - } - - protected void nonPublic() { - } - } - - private final static OptionalMethod<BaseClass> STRING_METHOD_RETURNS_ANY = - new OptionalMethod<>(null, "stringMethod"); - private final static OptionalMethod<BaseClass> STRING_METHOD_RETURNS_STRING = - new OptionalMethod<>(String.class, "stringMethod"); - private final static OptionalMethod<BaseClass> STRING_METHOD_RETURNS_INT = - new OptionalMethod<>(Integer.TYPE, "stringMethod"); - private final static OptionalMethod<BaseClass> VOID_METHOD_RETURNS_ANY = - new OptionalMethod<>(null, "voidMethod"); - private final static OptionalMethod<BaseClass> VOID_METHOD_RETURNS_VOID = - new OptionalMethod<>(Void.TYPE, "voidMethod"); - private final static OptionalMethod<BaseClass> SUBCLASS_METHOD_RETURNS_ANY = - new OptionalMethod<>(null, "subclassMethod"); - private final static OptionalMethod<BaseClass> SUBCLASS_METHOD_RETURNS_STRING = - new OptionalMethod<>(String.class, "subclassMethod"); - private final static OptionalMethod<BaseClass> SUBCLASS_METHOD_RETURNS_INT = - new OptionalMethod<>(Integer.TYPE, "subclassMethod"); - private final static OptionalMethod<BaseClass> METHOD_WITH_ARGS_WRONG_PARAMS = - new OptionalMethod<>(null, "methodWithArgs", Integer.class); - private final static OptionalMethod<BaseClass> METHOD_WITH_ARGS_CORRECT_PARAMS = - new OptionalMethod<>(null, "methodWithArgs", String.class); - - private final static OptionalMethod<BaseClass> THROWS_EXCEPTION = - new OptionalMethod<>(null, "throwsException"); - private final static OptionalMethod<BaseClass> THROWS_RUNTIME_EXCEPTION = - new OptionalMethod<>(null, "throwsRuntimeException"); - private final static OptionalMethod<BaseClass> NON_PUBLIC = - new OptionalMethod<>(null, "nonPublic"); - - @Test - public void isSupported() throws Exception { - { - BaseClass base = new BaseClass(); - assertTrue(STRING_METHOD_RETURNS_ANY.isSupported(base)); - assertTrue(STRING_METHOD_RETURNS_STRING.isSupported(base)); - assertFalse(STRING_METHOD_RETURNS_INT.isSupported(base)); - assertTrue(VOID_METHOD_RETURNS_ANY.isSupported(base)); - assertTrue(VOID_METHOD_RETURNS_VOID.isSupported(base)); - assertFalse(SUBCLASS_METHOD_RETURNS_ANY.isSupported(base)); - assertFalse(SUBCLASS_METHOD_RETURNS_STRING.isSupported(base)); - assertFalse(SUBCLASS_METHOD_RETURNS_INT.isSupported(base)); - assertFalse(METHOD_WITH_ARGS_WRONG_PARAMS.isSupported(base)); - assertFalse(METHOD_WITH_ARGS_CORRECT_PARAMS.isSupported(base)); - } - { - SubClass1 subClass1 = new SubClass1(); - assertTrue(STRING_METHOD_RETURNS_ANY.isSupported(subClass1)); - assertTrue(STRING_METHOD_RETURNS_STRING.isSupported(subClass1)); - assertFalse(STRING_METHOD_RETURNS_INT.isSupported(subClass1)); - assertTrue(VOID_METHOD_RETURNS_ANY.isSupported(subClass1)); - assertTrue(VOID_METHOD_RETURNS_VOID.isSupported(subClass1)); - assertTrue(SUBCLASS_METHOD_RETURNS_ANY.isSupported(subClass1)); - assertTrue(SUBCLASS_METHOD_RETURNS_STRING.isSupported(subClass1)); - assertFalse(SUBCLASS_METHOD_RETURNS_INT.isSupported(subClass1)); - assertFalse(METHOD_WITH_ARGS_WRONG_PARAMS.isSupported(subClass1)); - assertTrue(METHOD_WITH_ARGS_CORRECT_PARAMS.isSupported(subClass1)); - } - { - SubClass2 subClass2 = new SubClass2(); - assertTrue(STRING_METHOD_RETURNS_ANY.isSupported(subClass2)); - assertTrue(STRING_METHOD_RETURNS_STRING.isSupported(subClass2)); - assertFalse(STRING_METHOD_RETURNS_INT.isSupported(subClass2)); - assertTrue(VOID_METHOD_RETURNS_ANY.isSupported(subClass2)); - assertTrue(VOID_METHOD_RETURNS_VOID.isSupported(subClass2)); - assertTrue(SUBCLASS_METHOD_RETURNS_ANY.isSupported(subClass2)); - assertFalse(SUBCLASS_METHOD_RETURNS_STRING.isSupported(subClass2)); - assertTrue(SUBCLASS_METHOD_RETURNS_INT.isSupported(subClass2)); - assertFalse(METHOD_WITH_ARGS_WRONG_PARAMS.isSupported(subClass2)); - assertTrue(METHOD_WITH_ARGS_CORRECT_PARAMS.isSupported(subClass2)); - } - } - - @Test - public void invoke() throws Exception { - { - BaseClass base = new BaseClass(); - assertEquals("string", STRING_METHOD_RETURNS_STRING.invoke(base)); - assertEquals("string", STRING_METHOD_RETURNS_ANY.invoke(base)); - assertErrorOnInvoke(STRING_METHOD_RETURNS_INT, base); - assertNull(VOID_METHOD_RETURNS_ANY.invoke(base)); - assertNull(VOID_METHOD_RETURNS_VOID.invoke(base)); - assertErrorOnInvoke(SUBCLASS_METHOD_RETURNS_ANY, base); - assertErrorOnInvoke(SUBCLASS_METHOD_RETURNS_STRING, base); - assertErrorOnInvoke(SUBCLASS_METHOD_RETURNS_INT, base); - assertErrorOnInvoke(METHOD_WITH_ARGS_WRONG_PARAMS, base); - assertErrorOnInvoke(METHOD_WITH_ARGS_CORRECT_PARAMS, base); - } - { - SubClass1 subClass1 = new SubClass1(); - assertEquals("string", STRING_METHOD_RETURNS_STRING.invoke(subClass1)); - assertEquals("string", STRING_METHOD_RETURNS_ANY.invoke(subClass1)); - assertErrorOnInvoke(STRING_METHOD_RETURNS_INT, subClass1); - assertNull(VOID_METHOD_RETURNS_ANY.invoke(subClass1)); - assertNull(VOID_METHOD_RETURNS_VOID.invoke(subClass1)); - assertEquals("subclassMethod1", SUBCLASS_METHOD_RETURNS_ANY.invoke(subClass1)); - assertEquals("subclassMethod1", SUBCLASS_METHOD_RETURNS_STRING.invoke(subClass1)); - assertErrorOnInvoke(SUBCLASS_METHOD_RETURNS_INT, subClass1); - assertErrorOnInvoke(METHOD_WITH_ARGS_WRONG_PARAMS, subClass1); - assertEquals("arg", METHOD_WITH_ARGS_CORRECT_PARAMS.invoke(subClass1, "arg")); - } - - { - SubClass2 subClass2 = new SubClass2(); - assertEquals("string", STRING_METHOD_RETURNS_STRING.invoke(subClass2)); - assertEquals("string", STRING_METHOD_RETURNS_ANY.invoke(subClass2)); - assertErrorOnInvoke(STRING_METHOD_RETURNS_INT, subClass2); - assertNull(VOID_METHOD_RETURNS_ANY.invoke(subClass2)); - assertNull(VOID_METHOD_RETURNS_VOID.invoke(subClass2)); - assertEquals(1234, SUBCLASS_METHOD_RETURNS_ANY.invoke(subClass2)); - assertErrorOnInvoke(SUBCLASS_METHOD_RETURNS_STRING, subClass2); - assertEquals(1234, SUBCLASS_METHOD_RETURNS_INT.invoke(subClass2)); - assertErrorOnInvoke(METHOD_WITH_ARGS_WRONG_PARAMS, subClass2); - assertEquals("arg", METHOD_WITH_ARGS_CORRECT_PARAMS.invoke(subClass2, "arg")); - } - } - - @Test - public void invokeBadArgs() throws Exception { - SubClass1 subClass1 = new SubClass1(); - assertIllegalArgumentExceptionOnInvoke(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1); // no args - assertIllegalArgumentExceptionOnInvoke(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1, 123); - assertIllegalArgumentExceptionOnInvoke(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1, true); - assertIllegalArgumentExceptionOnInvoke(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1, - new Object()); - assertIllegalArgumentExceptionOnInvoke(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1, "one", - "two"); - } - - @Test - public void invokeWithException() throws Exception { - SubClass2 subClass2 = new SubClass2(); - try { - THROWS_EXCEPTION.invoke(subClass2); - } catch (InvocationTargetException expected) { - assertTrue(expected.getTargetException() instanceof IOException); - } - - try { - THROWS_RUNTIME_EXCEPTION.invoke(subClass2); - } catch (InvocationTargetException expected) { - assertTrue(expected.getTargetException() instanceof NumberFormatException); - } - } - - @Test - public void invokeNonPublic() throws Exception { - SubClass2 subClass2 = new SubClass2(); - assertFalse(NON_PUBLIC.isSupported(subClass2)); - assertErrorOnInvoke(NON_PUBLIC, subClass2); - } - - @Test - public void invokeOptional() throws Exception { - { - BaseClass base = new BaseClass(); - assertEquals("string", STRING_METHOD_RETURNS_STRING.invokeOptional(base)); - assertEquals("string", STRING_METHOD_RETURNS_ANY.invokeOptional(base)); - assertNull(STRING_METHOD_RETURNS_INT.invokeOptional(base)); - assertNull(VOID_METHOD_RETURNS_ANY.invokeOptional(base)); - assertNull(VOID_METHOD_RETURNS_VOID.invokeOptional(base)); - assertNull(SUBCLASS_METHOD_RETURNS_ANY.invokeOptional(base)); - assertNull(SUBCLASS_METHOD_RETURNS_STRING.invokeOptional(base)); - assertNull(SUBCLASS_METHOD_RETURNS_INT.invokeOptional(base)); - assertNull(METHOD_WITH_ARGS_WRONG_PARAMS.invokeOptional(base)); - assertNull(METHOD_WITH_ARGS_CORRECT_PARAMS.invokeOptional(base)); - } - { - SubClass1 subClass1 = new SubClass1(); - assertEquals("string", STRING_METHOD_RETURNS_STRING.invokeOptional(subClass1)); - assertEquals("string", STRING_METHOD_RETURNS_ANY.invokeOptional(subClass1)); - assertNull(STRING_METHOD_RETURNS_INT.invokeOptional(subClass1)); - assertNull(VOID_METHOD_RETURNS_ANY.invokeOptional(subClass1)); - assertNull(VOID_METHOD_RETURNS_VOID.invokeOptional(subClass1)); - assertEquals("subclassMethod1", SUBCLASS_METHOD_RETURNS_ANY.invokeOptional(subClass1)); - assertEquals("subclassMethod1", SUBCLASS_METHOD_RETURNS_STRING.invokeOptional(subClass1)); - assertNull(SUBCLASS_METHOD_RETURNS_INT.invokeOptional(subClass1)); - assertNull(METHOD_WITH_ARGS_WRONG_PARAMS.invokeOptional(subClass1)); - assertEquals("arg", METHOD_WITH_ARGS_CORRECT_PARAMS.invokeOptional(subClass1, "arg")); - } - - { - SubClass2 subClass2 = new SubClass2(); - assertEquals("string", STRING_METHOD_RETURNS_STRING.invokeOptional(subClass2)); - assertEquals("string", STRING_METHOD_RETURNS_ANY.invokeOptional(subClass2)); - assertNull(STRING_METHOD_RETURNS_INT.invokeOptional(subClass2)); - assertNull(VOID_METHOD_RETURNS_ANY.invokeOptional(subClass2)); - assertNull(VOID_METHOD_RETURNS_VOID.invokeOptional(subClass2)); - assertEquals(1234, SUBCLASS_METHOD_RETURNS_ANY.invokeOptional(subClass2)); - assertNull(SUBCLASS_METHOD_RETURNS_STRING.invokeOptional(subClass2)); - assertEquals(1234, SUBCLASS_METHOD_RETURNS_INT.invokeOptional(subClass2)); - assertNull(METHOD_WITH_ARGS_WRONG_PARAMS.invokeOptional(subClass2)); - assertEquals("arg", METHOD_WITH_ARGS_CORRECT_PARAMS.invokeOptional(subClass2, "arg")); - } - } - - @Test - public void invokeOptionalBadArgs() throws Exception { - SubClass1 subClass1 = new SubClass1(); - assertIllegalArgumentExceptionOnInvokeOptional(METHOD_WITH_ARGS_CORRECT_PARAMS, - subClass1); // no args - assertIllegalArgumentExceptionOnInvokeOptional(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1, 123); - assertIllegalArgumentExceptionOnInvokeOptional(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1, - true); - assertIllegalArgumentExceptionOnInvokeOptional(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1, - new Object()); - assertIllegalArgumentExceptionOnInvokeOptional(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1, - "one", "two"); - } - - @Test - public void invokeOptionalWithException() throws Exception { - SubClass2 subClass2 = new SubClass2(); - try { - THROWS_EXCEPTION.invokeOptional(subClass2); - } catch (InvocationTargetException expected) { - assertTrue(expected.getTargetException() instanceof IOException); - } - - try { - THROWS_RUNTIME_EXCEPTION.invokeOptional(subClass2); - } catch (InvocationTargetException expected) { - assertTrue(expected.getTargetException() instanceof NumberFormatException); - } - } - - @Test - @Ignore("Despite returning false for isSupported, invocation actually succeeds.") - public void invokeOptionalNonPublic() throws Exception { - SubClass2 subClass2 = new SubClass2(); - assertFalse(NON_PUBLIC.isSupported(subClass2)); - assertErrorOnInvokeOptional(NON_PUBLIC, subClass2); - } - - private static <T> void assertErrorOnInvoke( - OptionalMethod<T> optionalMethod, T base, Object... args) throws Exception { - try { - optionalMethod.invoke(base, args); - } catch (Error expected) { - return; - } - fail(); - } - - private static <T> void assertIllegalArgumentExceptionOnInvoke( - OptionalMethod<T> optionalMethod, T base, Object... args) throws Exception { - try { - optionalMethod.invoke(base, args); - fail(); - } catch (IllegalArgumentException expected) { - } - } - - private static <T> void assertErrorOnInvokeOptional( - OptionalMethod<T> optionalMethod, T base, Object... args) throws Exception { - try { - optionalMethod.invokeOptional(base, args); - } catch (Error expected) { - return; - } - fail(); - } - - private static <T> void assertIllegalArgumentExceptionOnInvokeOptional( - OptionalMethod<T> optionalMethod, T base, Object... args) throws Exception { - try { - optionalMethod.invokeOptional(base, args); - fail(); - } catch (IllegalArgumentException expected) { - } - } -} diff --git a/okhttp-tests/src/test/java/okhttp3/internal/tls/CertificatePinnerChainValidationTest.java b/okhttp-tests/src/test/java/okhttp3/internal/tls/CertificatePinnerChainValidationTest.java index ffb116eea7a35db35e56a4676ab99bc078b2773d..256afe67fec974641ee1beaaa3a4feaa43c07dc9 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/tls/CertificatePinnerChainValidationTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/tls/CertificatePinnerChainValidationTest.java @@ -37,8 +37,8 @@ import okhttp3.internal.platform.Platform; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.SocketPolicy; -import okhttp3.tls.HeldCertificate; import okhttp3.tls.HandshakeCertificates; +import okhttp3.tls.HeldCertificate; import org.junit.Rule; import org.junit.Test; @@ -335,8 +335,7 @@ public final class CertificatePinnerChainValidationTest { // http://hg.openjdk.java.net/jdk9/jdk9/jdk/file/2c1c21d11e58/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java#l596 String keystoreType = getPlatform().equals("jdk9") ? "JKS" : null; X509KeyManager x509KeyManager = newKeyManager(keystoreType, heldCertificate, intermediates); - X509TrustManager trustManager = newTrustManager( - keystoreType, Collections.<X509Certificate>emptyList()); + X509TrustManager trustManager = newTrustManager(keystoreType, Collections.emptyList()); SSLContext sslContext = Platform.get().getSSLContext(); sslContext.init(new KeyManager[] { x509KeyManager }, new TrustManager[] { trustManager }, new SecureRandom()); diff --git a/okhttp-tests/src/test/java/okhttp3/internal/tls/ClientAuthTest.java b/okhttp-tests/src/test/java/okhttp3/internal/tls/ClientAuthTest.java index ff3fb12270848af1eafe71a8f2775c24dec578f8..e88b9fef2548218d78081806600dadcf8423b0d2 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/tls/ClientAuthTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/tls/ClientAuthTest.java @@ -36,8 +36,8 @@ import okhttp3.Request; import okhttp3.Response; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; -import okhttp3.tls.HeldCertificate; import okhttp3.tls.HandshakeCertificates; +import okhttp3.tls.HeldCertificate; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -47,7 +47,7 @@ import static okhttp3.internal.platform.PlatformTest.getPlatform; import static okhttp3.tls.internal.TlsUtil.newKeyManager; import static okhttp3.tls.internal.TlsUtil.newTrustManager; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; public final class ClientAuthTest { @@ -149,7 +149,7 @@ public final class ClientAuthTest { Call call = client.newCall(new Request.Builder().url(server.url("/")).build()); Response response = call.execute(); assertEquals(new X500Principal("CN=Local Host"), response.handshake().peerPrincipal()); - assertEquals(null, response.handshake().localPrincipal()); + assertNull(response.handshake().localPrincipal()); assertEquals("abc", response.body().string()); } @@ -165,7 +165,7 @@ public final class ClientAuthTest { Call call = client.newCall(new Request.Builder().url(server.url("/")).build()); Response response = call.execute(); assertEquals(new X500Principal("CN=Local Host"), response.handshake().peerPrincipal()); - assertEquals(null, response.handshake().localPrincipal()); + assertNull(response.handshake().localPrincipal()); assertEquals("abc", response.body().string()); } diff --git a/okhttp-tests/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java b/okhttp-tests/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java index 6dd5bbbd7c2f2630354799960c60d5e6f04559cd..81f41b0442772611fdb8701d7edf5ccedad13937 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java @@ -28,6 +28,7 @@ import okhttp3.internal.Util; import org.junit.Ignore; import org.junit.Test; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -544,7 +545,7 @@ public final class HostnameVerifierTest { private X509Certificate certificate(String certificate) throws Exception { return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate( - new ByteArrayInputStream(certificate.getBytes(Util.UTF_8))); + new ByteArrayInputStream(certificate.getBytes(UTF_8))); } private SSLSession session(String certificate) throws Exception { diff --git a/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketHttpTest.java b/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketHttpTest.java index 1e17baa9c2ab4f0f45273744bcb46eef708facff..e4c29b837483f4305c157f9ecb2eb032e5dfe835 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketHttpTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketHttpTest.java @@ -27,7 +27,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; -import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Protocol; import okhttp3.RecordingEventListener; @@ -70,17 +69,17 @@ public final class WebSocketHttpTest { private OkHttpClient client = defaultClient().newBuilder() .writeTimeout(500, TimeUnit.MILLISECONDS) .readTimeout(500, TimeUnit.MILLISECONDS) - .addInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - Response response = chain.proceed(chain.request()); - assertNotNull(response.body()); // Ensure application interceptors never see a null body. - return response; - } + .addInterceptor(chain -> { + Response response = chain.proceed(chain.request()); + assertNotNull(response.body()); // Ensure application interceptors never see a null body. + return response; }) .build(); @After public void tearDown() { clientListener.assertExhausted(); + + // TODO: assert all connections are released once leaks are fixed } @Test public void textMessage() { @@ -88,10 +87,12 @@ public final class WebSocketHttpTest { WebSocket webSocket = newWebSocket(); clientListener.assertOpen(); - serverListener.assertOpen(); + WebSocket server = serverListener.assertOpen(); webSocket.send("Hello, WebSockets!"); serverListener.assertTextMessage("Hello, WebSockets!"); + + closeWebSockets(webSocket, server); } @Test public void binaryMessage() { @@ -99,10 +100,12 @@ public final class WebSocketHttpTest { WebSocket webSocket = newWebSocket(); clientListener.assertOpen(); - serverListener.assertOpen(); + WebSocket server = serverListener.assertOpen(); webSocket.send(ByteString.encodeUtf8("Hello!")); serverListener.assertBinaryMessage(ByteString.of(new byte[] {'H', 'e', 'l', 'l', 'o', '!'})); + + closeWebSockets(webSocket, server); } @Test public void nullStringThrows() { @@ -110,12 +113,15 @@ public final class WebSocketHttpTest { WebSocket webSocket = newWebSocket(); clientListener.assertOpen(); + WebSocket server = serverListener.assertOpen(); try { webSocket.send((String) null); fail(); } catch (NullPointerException e) { assertEquals("text == null", e.getMessage()); } + + closeWebSockets(webSocket, server); } @Test public void nullByteStringThrows() { @@ -123,23 +129,28 @@ public final class WebSocketHttpTest { WebSocket webSocket = newWebSocket(); clientListener.assertOpen(); + WebSocket server = serverListener.assertOpen(); try { webSocket.send((ByteString) null); fail(); } catch (NullPointerException e) { assertEquals("bytes == null", e.getMessage()); } + + closeWebSockets(webSocket, server); } @Test public void serverMessage() { webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener)); - newWebSocket(); + WebSocket webSocket = newWebSocket(); clientListener.assertOpen(); WebSocket server = serverListener.assertOpen(); server.send("Hello, WebSockets!"); clientListener.assertTextMessage("Hello, WebSockets!"); + + closeWebSockets(webSocket, server); } @Test public void throwingOnOpenFailsImmediately() { @@ -243,13 +254,10 @@ public final class WebSocketHttpTest { webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener)); newWebSocket(); - clientListener.assertOpen(); + WebSocket webSocket = clientListener.assertOpen(); WebSocket server = serverListener.assertOpen(); - server.close(1001, "bye"); - clientListener.assertClosing(1001, "bye"); - clientListener.assertExhausted(); - serverListener.assertExhausted(); + closeWebSockets(webSocket, server); } @Test public void non101RetainsBody() throws IOException { @@ -281,6 +289,8 @@ public final class WebSocketHttpTest { server.send("def"); clientListener.assertTextMessage("def"); + + closeWebSockets(webSocket, server); } @Test public void missingConnectionHeader() throws IOException { @@ -356,16 +366,15 @@ public final class WebSocketHttpTest { final AtomicInteger interceptedCount = new AtomicInteger(); client = client.newBuilder() - .addInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - assertNull(chain.request().body()); - Response response = chain.proceed(chain.request()); - assertEquals("Upgrade", response.header("Connection")); - assertTrue(response.body().source().exhausted()); - interceptedCount.incrementAndGet(); - return response; - } - }).build(); + .addInterceptor(chain -> { + assertNull(chain.request().body()); + Response response = chain.proceed(chain.request()); + assertEquals("Upgrade", response.header("Connection")); + assertTrue(response.body().source().exhausted()); + interceptedCount.incrementAndGet(); + return response; + }) + .build(); webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener)); @@ -376,24 +385,24 @@ public final class WebSocketHttpTest { WebSocket server = serverListener.assertOpen(); server.close(1000, null); + clientListener.assertClosing(1000, ""); + clientListener.assertClosed(1000, ""); } @Test public void webSocketAndNetworkInterceptors() { client = client.newBuilder() - .addNetworkInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) { - throw new AssertionError(); // Network interceptors don't execute. - } - }).build(); + .addNetworkInterceptor(chain -> { + throw new AssertionError(); // Network interceptors don't execute. + }) + .build(); webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener)); WebSocket webSocket = newWebSocket(); clientListener.assertOpen(); - webSocket.close(1000, null); - WebSocket server = serverListener.assertOpen(); - server.close(1000, null); + + closeWebSockets(webSocket, server); } @Test public void overflowOutgoingQueue() { @@ -538,7 +547,7 @@ public final class WebSocketHttpTest { @Test public void readTimeoutDoesNotApplyAcrossFrames() throws Exception { webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener)); - newWebSocket(); + WebSocket webSocket = newWebSocket(); clientListener.assertOpen(); WebSocket server = serverListener.assertOpen(); @@ -548,6 +557,8 @@ public final class WebSocketHttpTest { server.send("abc"); clientListener.assertTextMessage("abc"); + + closeWebSockets(webSocket, server); } @Test public void clientPingsServerOnInterval() throws Exception { @@ -577,6 +588,8 @@ public final class WebSocketHttpTest { // The server has never pinged the client. assertEquals(0, server.receivedPongCount()); assertEquals(0, webSocket.receivedPingCount()); + + closeWebSockets(webSocket, server); } @Test public void clientDoesNotPingServerByDefault() throws Exception { @@ -595,6 +608,8 @@ public final class WebSocketHttpTest { assertEquals(0, server.sentPingCount()); assertEquals(0, server.receivedPingCount()); assertEquals(0, server.receivedPongCount()); + + closeWebSockets(webSocket, server); } /** @@ -687,7 +702,7 @@ public final class WebSocketHttpTest { webServer.enqueue(new MockResponse() .withWebSocketUpgrade(serverListener)); - newWebSocket(); + WebSocket webSocket = newWebSocket(); clientListener.assertOpen(); WebSocket server = serverListener.assertOpen(); @@ -696,6 +711,8 @@ public final class WebSocketHttpTest { server.send("Hello, WebSockets!"); clientListener.assertTextMessage("Hello, WebSockets!"); + + closeWebSockets(webSocket, server); } /** @@ -745,10 +762,12 @@ public final class WebSocketHttpTest { RealWebSocket webSocket = newWebSocket(request); clientListener.assertOpen(); - serverListener.assertOpen(); + WebSocket server = serverListener.assertOpen(); webSocket.send("abc"); serverListener.assertTextMessage("abc"); + + closeWebSockets(webSocket, server); } private RealWebSocket newWebSocket() { @@ -761,4 +780,15 @@ public final class WebSocketHttpTest { webSocket.connect(client); return webSocket; } + + private void closeWebSockets(WebSocket webSocket, WebSocket server) { + server.close(1001, ""); + clientListener.assertClosing(1001, ""); + webSocket.close(1000, ""); + serverListener.assertClosing(1000, ""); + clientListener.assertClosed(1001, ""); + serverListener.assertClosed(1000, ""); + clientListener.assertExhausted(); + serverListener.assertExhausted(); + } } diff --git a/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketRecorder.java b/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketRecorder.java index f6256ac42f9757c5ae525406da63b9072fb11f46..e8d4353829818688543874236a160669909c838d 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketRecorder.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketRecorder.java @@ -17,6 +17,7 @@ package okhttp3.internal.ws; import java.io.IOException; import java.util.Arrays; +import java.util.Objects; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -24,7 +25,6 @@ import javax.annotation.Nullable; import okhttp3.Response; import okhttp3.WebSocket; import okhttp3.WebSocketListener; -import okhttp3.internal.Util; import okhttp3.internal.platform.Platform; import okio.ByteString; @@ -86,7 +86,7 @@ public final class WebSocketRecorder extends WebSocketListener { } @Override public void onClosing(WebSocket webSocket, int code, String reason) { - Platform.get().log(Platform.INFO, "[WS " + name + "] onClose " + code, null); + Platform.get().log(Platform.INFO, "[WS " + name + "] onClosing " + code, null); WebSocketListener delegate = this.delegate; if (delegate != null) { @@ -98,7 +98,7 @@ public final class WebSocketRecorder extends WebSocketListener { } @Override public void onClosed(WebSocket webSocket, int code, String reason) { - Platform.get().log(Platform.INFO, "[WS " + name + "] onClose " + code, null); + Platform.get().log(Platform.INFO, "[WS " + name + "] onClosed " + code, null); WebSocketListener delegate = this.delegate; if (delegate != null) { @@ -309,8 +309,8 @@ public final class WebSocketRecorder extends WebSocketListener { @Override public boolean equals(Object other) { return other instanceof Message - && Util.equal(((Message) other).bytes, bytes) - && Util.equal(((Message) other).string, string); + && Objects.equals(((Message) other).bytes, bytes) + && Objects.equals(((Message) other).string, string); } } diff --git a/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketWriterTest.java b/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketWriterTest.java index e183d0e2a8d34a56c79493b305d9060b68772503..48684f553a4e681116455b82ffa0f76836b187f2 100644 --- a/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketWriterTest.java +++ b/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketWriterTest.java @@ -28,7 +28,6 @@ import okio.Sink; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; -import org.junit.runner.Description; import org.junit.runners.model.Statement; import static okhttp3.TestUtil.repeat; @@ -36,7 +35,6 @@ import static okhttp3.internal.ws.WebSocketProtocol.OPCODE_BINARY; import static okhttp3.internal.ws.WebSocketProtocol.OPCODE_TEXT; import static okhttp3.internal.ws.WebSocketProtocol.PAYLOAD_BYTE_MAX; import static okhttp3.internal.ws.WebSocketProtocol.PAYLOAD_SHORT_MAX; -import static okhttp3.internal.ws.WebSocketProtocol.toggleMask; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -49,14 +47,10 @@ public final class WebSocketWriterTest { * Check all data as verified inside of the test. We do this in a rule instead of @After so that * exceptions thrown from the test do not cause this check to fail. */ - @Rule public final TestRule noDataLeftBehind = new TestRule() { - @Override public Statement apply(final Statement base, Description description) { - return new Statement() { - @Override public void evaluate() throws Throwable { - base.evaluate(); - assertEquals("Data not empty", "", data.readByteString().hex()); - } - }; + @Rule public final TestRule noDataLeftBehind = (base, description) -> new Statement() { + @Override public void evaluate() throws Throwable { + base.evaluate(); + assertEquals("Data not empty", "", data.readByteString().hex()); } }; diff --git a/okhttp-tls/pom.xml b/okhttp-tls/pom.xml index 7ee4267c7277505b1bec98ba7f00f335b898f798..2f0d9537abec629b99b737a6fe0a953b4513ad34 100644 --- a/okhttp-tls/pom.xml +++ b/okhttp-tls/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3</groupId> <artifactId>parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>okhttp-tls</artifactId> @@ -46,8 +46,9 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>2.10.4</version> + <version>3.0.1</version> <configuration> + <excludePackageNames>okhttp3.tls.internal:okhttp3.tls.internal.*</excludePackageNames> <links> <link>http://square.github.io/okhttp/javadoc/</link> </links> @@ -56,7 +57,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.1.1</version> <configuration> <archive> <manifestEntries> diff --git a/okhttp-tls/src/main/java/okhttp3/tls/HeldCertificate.java b/okhttp-tls/src/main/java/okhttp3/tls/HeldCertificate.java index 8709dc14382eb36bda957911d6aa98d1932e6cda..c659f58196517ee29d059da0f184c0cf3604e860 100644 --- a/okhttp-tls/src/main/java/okhttp3/tls/HeldCertificate.java +++ b/okhttp-tls/src/main/java/okhttp3/tls/HeldCertificate.java @@ -400,8 +400,7 @@ public final class HeldCertificate { } try { - X509Certificate certificate = generator.generateX509Certificate( - signedByKeyPair.getPrivate()); + X509Certificate certificate = generator.generate(signedByKeyPair.getPrivate()); return new HeldCertificate(heldKeyPair, certificate); } catch (GeneralSecurityException e) { throw new AssertionError(e); diff --git a/okhttp-tls/src/test/java/okhttp3/tls/HandshakeCertificatesTest.java b/okhttp-tls/src/test/java/okhttp3/tls/HandshakeCertificatesTest.java index c2e4f3a45f552c7eeaa84eb2319d8ac167859ffc..6892e14f0be07b962d80bd1c9d59ee24d6ad0253 100644 --- a/okhttp-tls/src/test/java/okhttp3/tls/HandshakeCertificatesTest.java +++ b/okhttp-tls/src/test/java/okhttp3/tls/HandshakeCertificatesTest.java @@ -25,7 +25,6 @@ import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -33,12 +32,12 @@ import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import javax.net.ssl.SSLSocket; import okhttp3.Handshake; -import okhttp3.internal.Util; import okio.ByteString; import org.junit.After; import org.junit.Before; import org.junit.Test; +import static okhttp3.internal.Util.closeQuietly; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -52,7 +51,7 @@ public final class HandshakeCertificatesTest { @After public void tearDown() { executorService.shutdown(); - Util.closeQuietly(serverSocket); + closeQuietly(serverSocket); } @Test public void clientAndServer() throws Exception { @@ -149,45 +148,39 @@ public final class HandshakeCertificatesTest { return new InetSocketAddress(serverAddress, serverSocket.getLocalPort()); } - private Future<Handshake> doServerHandshake(final HandshakeCertificates server) { - return executorService.submit(new Callable<Handshake>() { - @Override public Handshake call() throws Exception { - Socket rawSocket = null; - SSLSocket sslSocket = null; - try { - rawSocket = serverSocket.accept(); - sslSocket = (SSLSocket) server.sslSocketFactory().createSocket(rawSocket, - rawSocket.getInetAddress().getHostAddress(), rawSocket.getPort(), - true /* autoClose */); - sslSocket.setUseClientMode(false); - sslSocket.setWantClientAuth(true); - sslSocket.startHandshake(); - return Handshake.get(sslSocket.getSession()); - } finally { - Util.closeQuietly(rawSocket); - Util.closeQuietly(sslSocket); - } + private Future<Handshake> doServerHandshake(HandshakeCertificates server) { + return executorService.submit(() -> { + Socket rawSocket = null; + SSLSocket sslSocket = null; + try { + rawSocket = serverSocket.accept(); + sslSocket = (SSLSocket) server.sslSocketFactory().createSocket(rawSocket, + rawSocket.getInetAddress().getHostAddress(), rawSocket.getPort(), true /* autoClose */); + sslSocket.setUseClientMode(false); + sslSocket.setWantClientAuth(true); + sslSocket.startHandshake(); + return Handshake.get(sslSocket.getSession()); + } finally { + closeQuietly(rawSocket); + closeQuietly(sslSocket); } }); } private Future<Handshake> doClientHandshake( - final HandshakeCertificates client, final InetSocketAddress serverAddress) { - return executorService.submit(new Callable<Handshake>() { - @Override public Handshake call() throws Exception { - Socket rawSocket = SocketFactory.getDefault().createSocket(); - rawSocket.connect(serverAddress); - SSLSocket sslSocket = null; - try { - sslSocket = (SSLSocket) client.sslSocketFactory().createSocket(rawSocket, - rawSocket.getInetAddress().getHostAddress(), rawSocket.getPort(), - true /* autoClose */); - sslSocket.startHandshake(); - return Handshake.get(sslSocket.getSession()); - } finally { - Util.closeQuietly(rawSocket); - Util.closeQuietly(sslSocket); - } + HandshakeCertificates client, InetSocketAddress serverAddress) { + return executorService.submit(() -> { + Socket rawSocket = SocketFactory.getDefault().createSocket(); + rawSocket.connect(serverAddress); + SSLSocket sslSocket = null; + try { + sslSocket = (SSLSocket) client.sslSocketFactory().createSocket(rawSocket, + rawSocket.getInetAddress().getHostAddress(), rawSocket.getPort(), true /* autoClose */); + sslSocket.startHandshake(); + return Handshake.get(sslSocket.getSession()); + } finally { + closeQuietly(rawSocket); + closeQuietly(sslSocket); } }); } diff --git a/okhttp-urlconnection/pom.xml b/okhttp-urlconnection/pom.xml index 1bdc421954d5689dee9b4d9492d22c77fee6ce46..04b16b2bb4c7f39fcb4bd68e3012e25e0750d986 100644 --- a/okhttp-urlconnection/pom.xml +++ b/okhttp-urlconnection/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3</groupId> <artifactId>parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>okhttp-urlconnection</artifactId> @@ -60,7 +60,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>2.10.4</version> + <version>3.0.1</version> <configuration> <excludePackageNames>okhttp3.internal:okhttp3.internal.*</excludePackageNames> <links> @@ -71,7 +71,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.1.1</version> <configuration> <archive> <manifestEntries> diff --git a/okhttp-urlconnection/src/main/java/okhttp3/JavaNetAuthenticator.java b/okhttp-urlconnection/src/main/java/okhttp3/JavaNetAuthenticator.java index 94eb4558c2949b457d7a5200b8cc377490cb0590..f1892b7199ac1f2388e1372811114efb05f80da3 100644 --- a/okhttp-urlconnection/src/main/java/okhttp3/JavaNetAuthenticator.java +++ b/okhttp-urlconnection/src/main/java/okhttp3/JavaNetAuthenticator.java @@ -68,7 +68,7 @@ public final class JavaNetAuthenticator implements Authenticator { } private InetAddress getConnectToInetAddress(Proxy proxy, HttpUrl url) throws IOException { - return (proxy != null && proxy.type() != Proxy.Type.DIRECT) + return proxy.type() != Proxy.Type.DIRECT ? ((InetSocketAddress) proxy.address()).getAddress() : InetAddress.getByName(url.host()); } diff --git a/okhttp-urlconnection/src/main/java/okhttp3/JavaNetCookieJar.java b/okhttp-urlconnection/src/main/java/okhttp3/JavaNetCookieJar.java index c6cca30184e1583ad643551b2039490521afdc1e..6faf288de7fd5ad20f7823bfecb63ad134d733c9 100644 --- a/okhttp-urlconnection/src/main/java/okhttp3/JavaNetCookieJar.java +++ b/okhttp-urlconnection/src/main/java/okhttp3/JavaNetCookieJar.java @@ -78,7 +78,7 @@ public final class JavaNetCookieJar implements CookieJar { return cookies != null ? Collections.unmodifiableList(cookies) - : Collections.<Cookie>emptyList(); + : Collections.emptyList(); } /** diff --git a/okhttp-urlconnection/src/main/java/okhttp3/OkUrlFactory.java b/okhttp-urlconnection/src/main/java/okhttp3/OkUrlFactory.java index f4e88161c9d757207afae8381bbcdabdff0fa881..8e4591e7844c3705a3bebb4942d879d68bad7f76 100644 --- a/okhttp-urlconnection/src/main/java/okhttp3/OkUrlFactory.java +++ b/okhttp-urlconnection/src/main/java/okhttp3/OkUrlFactory.java @@ -21,6 +21,7 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; +import javax.annotation.Nullable; import okhttp3.internal.URLFilter; import okhttp3.internal.annotations.EverythingIsNonNull; import okhttp3.internal.huc.OkHttpURLConnection; @@ -65,7 +66,7 @@ public final class OkUrlFactory implements URLStreamHandlerFactory, Cloneable { return open(url, client.proxy()); } - HttpURLConnection open(URL url, Proxy proxy) { + HttpURLConnection open(URL url, @Nullable Proxy proxy) { String protocol = url.getProtocol(); OkHttpClient copy = client.newBuilder() .proxy(proxy) diff --git a/okhttp-urlconnection/src/main/java/okhttp3/internal/JavaNetHeaders.java b/okhttp-urlconnection/src/main/java/okhttp3/internal/JavaNetHeaders.java index 0b7a02e1f9619c0d4832a3f401c1680bbf5170b2..1b4290a7ef38956cc8b48afcc2927033ae08b8c5 100644 --- a/okhttp-urlconnection/src/main/java/okhttp3/internal/JavaNetHeaders.java +++ b/okhttp-urlconnection/src/main/java/okhttp3/internal/JavaNetHeaders.java @@ -27,18 +27,16 @@ public final class JavaNetHeaders { private JavaNetHeaders() { } - private static final Comparator<String> FIELD_NAME_COMPARATOR = new Comparator<String>() { + private static final Comparator<String> FIELD_NAME_COMPARATOR = (a, b) -> { // @FindBugsSuppressWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ") - @Override public int compare(String a, String b) { - if (a == b) { - return 0; - } else if (a == null) { - return -1; - } else if (b == null) { - return 1; - } else { - return String.CASE_INSENSITIVE_ORDER.compare(a, b); - } + if (a == b) { + return 0; + } else if (a == null) { + return -1; + } else if (b == null) { + return 1; + } else { + return String.CASE_INSENSITIVE_ORDER.compare(a, b); } }; diff --git a/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/DelegatingHttpsURLConnection.java b/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/DelegatingHttpsURLConnection.java index a051bf21982b01707de9953d99c1d95f564b9417..ae066f64b702bb0315a94ae937d06b6a8ca16dbd 100644 --- a/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/DelegatingHttpsURLConnection.java +++ b/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/DelegatingHttpsURLConnection.java @@ -134,7 +134,6 @@ abstract class DelegatingHttpsURLConnection extends HttpsURLConnection { return delegate.getContent(); } - @SuppressWarnings("unchecked") // Spec does not generify @Override public Object getContent(Class[] types) throws IOException { return delegate.getContent(types); } @@ -147,7 +146,7 @@ abstract class DelegatingHttpsURLConnection extends HttpsURLConnection { return delegate.getContentLength(); } - @IgnoreJRERequirement // Should only be invoked on Java 7+. + @IgnoreJRERequirement // Should only be invoked on Java 8+ or Android API 24+. @Override public long getContentLengthLong() { return delegate.getContentLengthLong(); } @@ -196,7 +195,7 @@ abstract class DelegatingHttpsURLConnection extends HttpsURLConnection { return delegate.getHeaderField(key); } - @IgnoreJRERequirement // Should only be invoked on Java 7+. + @IgnoreJRERequirement // Should only be invoked on Java 8+ or Android API 24+. @Override public long getHeaderFieldLong(String field, long defaultValue) { return delegate.getHeaderFieldLong(field, defaultValue); } @@ -261,7 +260,7 @@ abstract class DelegatingHttpsURLConnection extends HttpsURLConnection { delegate.setDoOutput(newValue); } - @IgnoreJRERequirement // Should only be invoked on Java 7+. + @IgnoreJRERequirement // Should only be invoked on Java 8+ or Android API 24+. @Override public void setFixedLengthStreamingMode(long contentLength) { delegate.setFixedLengthStreamingMode(contentLength); } diff --git a/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/OkHttpURLConnection.java b/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/OkHttpURLConnection.java index 31e532d0ad40684f3c6ee7b3c8b235bc8ddb9d21..1286cb7f53861886a3ae63d685d2b4d40b9ce630 100644 --- a/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/OkHttpURLConnection.java +++ b/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/OkHttpURLConnection.java @@ -52,6 +52,7 @@ import okhttp3.Response; import okhttp3.internal.Internal; import okhttp3.internal.JavaNetHeaders; import okhttp3.internal.URLFilter; +import okhttp3.internal.Util; import okhttp3.internal.Version; import okhttp3.internal.http.HttpDate; import okhttp3.internal.http.HttpHeaders; @@ -414,7 +415,7 @@ public final class OkHttpURLConnection extends HttpURLConnection implements Call } private String defaultUserAgent() { - String agent = System.getProperty("http.agent"); + String agent = Util.getSystemProperty("http.agent", null); return agent != null ? toHumanReadableAscii(agent) : Version.userAgent(); } @@ -594,13 +595,11 @@ public final class OkHttpURLConnection extends HttpURLConnection implements Call } static final class UnexpectedException extends IOException { - static final Interceptor INTERCEPTOR = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - try { - return chain.proceed(chain.request()); - } catch (Error | RuntimeException e) { - throw new UnexpectedException(e); - } + static final Interceptor INTERCEPTOR = chain -> { + try { + return chain.proceed(chain.request()); + } catch (Error | RuntimeException e) { + throw new UnexpectedException(e); } }; diff --git a/okhttp-urlconnection/src/test/java/okhttp3/OkUrlFactoryTest.java b/okhttp-urlconnection/src/test/java/okhttp3/OkUrlFactoryTest.java index 914725fb84cf5806c981d02b14d2885a0aa97512..1476dc69c4a9817bcc801e62d9093e88d570a7a9 100644 --- a/okhttp-urlconnection/src/test/java/okhttp3/OkUrlFactoryTest.java +++ b/okhttp-urlconnection/src/test/java/okhttp3/OkUrlFactoryTest.java @@ -14,7 +14,6 @@ import java.util.Locale; import java.util.TimeZone; import java.util.concurrent.TimeUnit; import javax.net.ssl.HttpsURLConnection; -import okhttp3.internal.URLFilter; import okhttp3.internal.huc.OkHttpURLConnection; import okhttp3.internal.io.InMemoryFileSystem; import okhttp3.mockwebserver.MockResponse; @@ -162,13 +161,8 @@ public class OkUrlFactoryTest { server.enqueue(new MockResponse() .setBody("B")); final URL blockedURL = server.url("/a").url(); - factory.setUrlFilter(new URLFilter() { - @Override - public void checkURLPermitted(URL url) throws IOException { - if (blockedURL.equals(url)) { - throw new IOException("Blocked"); - } - } + factory.setUrlFilter(url -> { + if (blockedURL.equals(url)) throw new IOException("Blocked"); }); try { HttpURLConnection connection = factory.open(server.url("/a").url()); @@ -195,13 +189,8 @@ public class OkUrlFactoryTest { handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager()) .followSslRedirects(true) .build()); - factory.setUrlFilter(new URLFilter() { - @Override - public void checkURLPermitted(URL url) throws IOException { - if (blockedURL.equals(url)) { - throw new IOException("Blocked"); - } - } + factory.setUrlFilter(url -> { + if (blockedURL.equals(url)) throw new IOException("Blocked"); }); server.enqueue(new MockResponse() diff --git a/okhttp-urlconnection/src/test/java/okhttp3/UrlConnectionCacheTest.java b/okhttp-urlconnection/src/test/java/okhttp3/UrlConnectionCacheTest.java index 23ff40c89ebb124de5293320de49ba51a5af2d1a..d9434abddfbbe77a296e333a529e1cc230cea856 100644 --- a/okhttp-urlconnection/src/test/java/okhttp3/UrlConnectionCacheTest.java +++ b/okhttp-urlconnection/src/test/java/okhttp3/UrlConnectionCacheTest.java @@ -16,12 +16,10 @@ package okhttp3; -import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; import java.net.CookieManager; import java.net.HttpURLConnection; @@ -40,7 +38,6 @@ import java.util.TimeZone; import java.util.concurrent.TimeUnit; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSession; import okhttp3.internal.Internal; import okhttp3.internal.io.InMemoryFileSystem; import okhttp3.internal.platform.Platform; @@ -50,6 +47,7 @@ import okhttp3.mockwebserver.RecordedRequest; import okhttp3.tls.HandshakeCertificates; import okio.Buffer; import okio.BufferedSink; +import okio.BufferedSource; import okio.GzipSink; import okio.Okio; import org.junit.After; @@ -70,11 +68,7 @@ import static org.junit.Assume.assumeFalse; /** Test caching with {@link OkUrlFactory}. */ public final class UrlConnectionCacheTest { - private static final HostnameVerifier NULL_HOSTNAME_VERIFIER = new HostnameVerifier() { - @Override public boolean verify(String s, SSLSession sslSession) { - return true; - } - }; + private static final HostnameVerifier NULL_HOSTNAME_VERIFIER = (name, session) -> true; @Rule public MockWebServer server = new MockWebServer(); @Rule public MockWebServer server2 = new MockWebServer(); @@ -428,15 +422,15 @@ public final class UrlConnectionCacheTest { server.enqueue(truncateViolently(response, 16)); server.enqueue(new MockResponse().setBody("Request #2")); - BufferedReader reader = new BufferedReader( - new InputStreamReader(urlFactory.open(server.url("/").url()).getInputStream())); - assertEquals("ABCDE", reader.readLine()); + BufferedSource source = Okio.buffer(Okio.source( + urlFactory.open(server.url("/").url()).getInputStream())); + assertEquals("ABCDE\n", source.readUtf8(6)); try { - reader.readLine(); + source.readUtf8(21); fail("This implementation silently ignored a truncated HTTP body."); } catch (IOException expected) { } finally { - reader.close(); + source.close(); } assertEquals(1, cache.writeAbortCount()); @@ -1501,7 +1495,7 @@ public final class UrlConnectionCacheTest { URLConnection connection2 = urlFactory.open(server.url("/").url()); assertEquals("A", readAscii(connection2)); - assertEquals(null, connection2.getHeaderField("Warning")); + assertNull(connection2.getHeaderField("Warning")); } @Test public void getHeadersRetainsCached200LevelWarnings() throws Exception { @@ -1539,7 +1533,7 @@ public final class UrlConnectionCacheTest { // cache miss; seed the cache HttpURLConnection connection1 = urlFactory.open(server.url("/a").url()); assertEquals("A", readAscii(connection1)); - assertEquals(null, connection1.getHeaderField("Allow")); + assertNull(connection1.getHeaderField("Allow")); // conditional cache hit; update the cache HttpURLConnection connection2 = urlFactory.open(server.url("/a").url()); diff --git a/okhttp/pom.xml b/okhttp/pom.xml index b7adb196faaa83085149e5297786076a21e929b9..4f28280c06e1690b008e655c0fe512fa39cbf66f 100644 --- a/okhttp/pom.xml +++ b/okhttp/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3</groupId> <artifactId>parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>okhttp</artifactId> @@ -45,7 +45,7 @@ <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>templating-maven-plugin</artifactId> - <version>1.0-alpha-3</version> + <version>1.0.0</version> <executions> <execution> <goals> @@ -57,7 +57,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>2.10.4</version> + <version>3.0.1</version> <configuration> <excludePackageNames>okhttp3.internal:okhttp3.internal.*</excludePackageNames> <links> @@ -68,7 +68,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.1.1</version> <configuration> <archive> <manifestEntries> diff --git a/okhttp/src/main/java/okhttp3/Address.java b/okhttp/src/main/java/okhttp3/Address.java index 9df666ad786f3214c9d065da2dcfd1c7aab8c286..5ad2a8d455e8a336da36c9269c556e43b04410c4 100644 --- a/okhttp/src/main/java/okhttp3/Address.java +++ b/okhttp/src/main/java/okhttp3/Address.java @@ -18,14 +18,13 @@ package okhttp3; import java.net.Proxy; import java.net.ProxySelector; import java.util.List; +import java.util.Objects; import javax.annotation.Nullable; import javax.net.SocketFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSocketFactory; import okhttp3.internal.Util; -import static okhttp3.internal.Util.equal; - /** * A specification for a connection to an origin server. For simple connections, this is the * server's hostname and port. If an explicit proxy is requested (or {@linkplain Proxy#NO_PROXY no @@ -165,10 +164,10 @@ public final class Address { result = 31 * result + protocols.hashCode(); result = 31 * result + connectionSpecs.hashCode(); result = 31 * result + proxySelector.hashCode(); - result = 31 * result + (proxy != null ? proxy.hashCode() : 0); - result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0); - result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0); - result = 31 * result + (certificatePinner != null ? certificatePinner.hashCode() : 0); + result = 31 * result + Objects.hashCode(proxy); + result = 31 * result + Objects.hashCode(sslSocketFactory); + result = 31 * result + Objects.hashCode(hostnameVerifier); + result = 31 * result + Objects.hashCode(certificatePinner); return result; } @@ -178,10 +177,10 @@ public final class Address { && this.protocols.equals(that.protocols) && this.connectionSpecs.equals(that.connectionSpecs) && this.proxySelector.equals(that.proxySelector) - && equal(this.proxy, that.proxy) - && equal(this.sslSocketFactory, that.sslSocketFactory) - && equal(this.hostnameVerifier, that.hostnameVerifier) - && equal(this.certificatePinner, that.certificatePinner) + && Objects.equals(this.proxy, that.proxy) + && Objects.equals(this.sslSocketFactory, that.sslSocketFactory) + && Objects.equals(this.hostnameVerifier, that.hostnameVerifier) + && Objects.equals(this.certificatePinner, that.certificatePinner) && this.url().port() == that.url().port(); } diff --git a/okhttp/src/main/java/okhttp3/Authenticator.java b/okhttp/src/main/java/okhttp3/Authenticator.java index 10482ffeada4d1f56221570cbf5c823faeb081c5..6e1d1a4fd5e703763604a62041e802573bdcb07c 100644 --- a/okhttp/src/main/java/okhttp3/Authenticator.java +++ b/okhttp/src/main/java/okhttp3/Authenticator.java @@ -97,11 +97,7 @@ import javax.annotation.Nullable; */ public interface Authenticator { /** An authenticator that knows no credentials and makes no attempt to authenticate. */ - Authenticator NONE = new Authenticator() { - @Override public Request authenticate(@Nullable Route route, Response response) { - return null; - } - }; + Authenticator NONE = (route, response) -> null; /** * Returns a request that includes a credential to satisfy an authentication challenge in {@code diff --git a/okhttp/src/main/java/okhttp3/Cache.java b/okhttp/src/main/java/okhttp3/Cache.java index 343df45113a1182afd88236ce9c37466af41c1ac..2ba97ffd0fa814a7833275b3701049ebdae24a10 100644 --- a/okhttp/src/main/java/okhttp3/Cache.java +++ b/okhttp/src/main/java/okhttp3/Cache.java @@ -141,11 +141,11 @@ public final class Cache implements Closeable, Flushable { private static final int ENTRY_COUNT = 2; final InternalCache internalCache = new InternalCache() { - @Override public Response get(Request request) throws IOException { + @Override public @Nullable Response get(Request request) throws IOException { return Cache.this.get(request); } - @Override public CacheRequest put(Response response) throws IOException { + @Override public @Nullable CacheRequest put(Response response) throws IOException { return Cache.this.put(response); } @@ -339,16 +339,13 @@ public final class Cache implements Closeable, Flushable { canRemove = false; // Prevent delegate.remove() on the wrong item! while (delegate.hasNext()) { - DiskLruCache.Snapshot snapshot = delegate.next(); - try { + try (DiskLruCache.Snapshot snapshot = delegate.next()) { BufferedSource metadata = Okio.buffer(snapshot.getSource(ENTRY_METADATA)); nextUrl = metadata.readUtf8LineStrict(); return true; } catch (IOException ignored) { // We couldn't read the metadata for this snapshot; possibly because the host filesystem // has disappeared! Skip it. - } finally { - snapshot.close(); } } diff --git a/okhttp/src/main/java/okhttp3/CertificatePinner.java b/okhttp/src/main/java/okhttp3/CertificatePinner.java index d5093645448afb2b443b87c9e92cc69e815b37d9..474c245ae6eaa30b5127e0d5af61b33d56485e99 100644 --- a/okhttp/src/main/java/okhttp3/CertificatePinner.java +++ b/okhttp/src/main/java/okhttp3/CertificatePinner.java @@ -22,14 +22,13 @@ import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import javax.annotation.Nullable; import javax.net.ssl.SSLPeerUnverifiedException; import okhttp3.internal.tls.CertificateChainCleaner; import okio.ByteString; -import static okhttp3.internal.Util.equal; - /** * Constrains which certificates are trusted. Pinning certificates defends against attacks on * certificate authorities. It also prevents connections through man-in-the-middle certificate @@ -139,12 +138,13 @@ public final class CertificatePinner { @Override public boolean equals(@Nullable Object other) { if (other == this) return true; return other instanceof CertificatePinner - && (equal(certificateChainCleaner, ((CertificatePinner) other).certificateChainCleaner) + && (Objects.equals(certificateChainCleaner, + ((CertificatePinner) other).certificateChainCleaner) && pins.equals(((CertificatePinner) other).pins)); } @Override public int hashCode() { - int result = certificateChainCleaner != null ? certificateChainCleaner.hashCode() : 0; + int result = Objects.hashCode(certificateChainCleaner); result = 31 * result + pins.hashCode(); return result; } @@ -228,7 +228,7 @@ public final class CertificatePinner { /** Returns a certificate pinner that uses {@code certificateChainCleaner}. */ CertificatePinner withCertificateChainCleaner( @Nullable CertificateChainCleaner certificateChainCleaner) { - return equal(this.certificateChainCleaner, certificateChainCleaner) + return Objects.equals(this.certificateChainCleaner, certificateChainCleaner) ? this : new CertificatePinner(pins, certificateChainCleaner); } diff --git a/okhttp/src/main/java/okhttp3/Challenge.java b/okhttp/src/main/java/okhttp3/Challenge.java index bff1cb830e322864b1bf476a757ae28118714a6f..0fb4c868a60ab1363656ce91e9e78176a9e53679 100644 --- a/okhttp/src/main/java/okhttp3/Challenge.java +++ b/okhttp/src/main/java/okhttp3/Challenge.java @@ -21,10 +21,10 @@ import java.util.Map; import java.util.Map.Entry; import javax.annotation.Nullable; +import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.util.Collections.singletonMap; import static java.util.Collections.unmodifiableMap; import static java.util.Locale.US; -import static okhttp3.internal.Util.ISO_8859_1; /** An RFC 7235 challenge. */ public final class Challenge { diff --git a/okhttp/src/main/java/okhttp3/CipherSuite.java b/okhttp/src/main/java/okhttp3/CipherSuite.java index ec1875359b18342ca987e5d0860a59e28844b76a..376e1fe0fabc9a5ea62dd75f9a1be9e406ddb2a7 100644 --- a/okhttp/src/main/java/okhttp3/CipherSuite.java +++ b/okhttp/src/main/java/okhttp3/CipherSuite.java @@ -46,18 +46,16 @@ public final class CipherSuite { * the "TLS_" or "SSL_" prefix which is not consistent across platforms. In particular some IBM * JVMs use the "SSL_" prefix everywhere whereas Oracle JVMs mix "TLS_" and "SSL_". */ - static final Comparator<String> ORDER_BY_NAME = new Comparator<String>() { - @Override public int compare(String a, String b) { - for (int i = 4, limit = Math.min(a.length(), b.length()); i < limit; i++) { - char charA = a.charAt(i); - char charB = b.charAt(i); - if (charA != charB) return charA < charB ? -1 : 1; - } - int lengthA = a.length(); - int lengthB = b.length(); - if (lengthA != lengthB) return lengthA < lengthB ? -1 : 1; - return 0; + static final Comparator<String> ORDER_BY_NAME = (a, b) -> { + for (int i = 4, limit = Math.min(a.length(), b.length()); i < limit; i++) { + char charA = a.charAt(i); + char charB = b.charAt(i); + if (charA != charB) return charA < charB ? -1 : 1; } + int lengthA = a.length(); + int lengthB = b.length(); + if (lengthA != lengthB) return lengthA < lengthB ? -1 : 1; + return 0; }; /** diff --git a/okhttp/src/main/java/okhttp3/ConnectionPool.java b/okhttp/src/main/java/okhttp3/ConnectionPool.java index 605f27566106485a09f27695c868726f446ca87f..2c0c6fffbd7d079220b0381abd356cb2f690eea2 100644 --- a/okhttp/src/main/java/okhttp3/ConnectionPool.java +++ b/okhttp/src/main/java/okhttp3/ConnectionPool.java @@ -49,24 +49,22 @@ public final class ConnectionPool { */ private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */, Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS, - new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true)); + new SynchronousQueue<>(), Util.threadFactory("OkHttp ConnectionPool", true)); /** The maximum number of idle connections for each address. */ private final int maxIdleConnections; private final long keepAliveDurationNs; - private final Runnable cleanupRunnable = new Runnable() { - @Override public void run() { - while (true) { - long waitNanos = cleanup(System.nanoTime()); - if (waitNanos == -1) return; - if (waitNanos > 0) { - long waitMillis = waitNanos / 1000000L; - waitNanos -= (waitMillis * 1000000L); - synchronized (ConnectionPool.this) { - try { - ConnectionPool.this.wait(waitMillis, (int) waitNanos); - } catch (InterruptedException ignored) { - } + private final Runnable cleanupRunnable = () -> { + while (true) { + long waitNanos = cleanup(System.nanoTime()); + if (waitNanos == -1) return; + if (waitNanos > 0) { + long waitMillis = waitNanos / 1000000L; + waitNanos -= (waitMillis * 1000000L); + synchronized (ConnectionPool.this) { + try { + ConnectionPool.this.wait(waitMillis, (int) waitNanos); + } catch (InterruptedException ignored) { } } } @@ -105,29 +103,23 @@ public final class ConnectionPool { return total; } - /** - * Returns total number of connections in the pool. Note that prior to OkHttp 2.7 this included - * only idle connections and HTTP/2 connections. Since OkHttp 2.7 this includes all connections, - * both active and inactive. Use {@link #idleConnectionCount()} to count connections not currently - * in use. - */ + /** Returns total number of connections in the pool. */ public synchronized int connectionCount() { return connections.size(); } /** - * Returns a recycled connection to {@code address}, or null if no such connection exists. The - * route is null if the address has not yet been routed. + * Acquires a recycled connection to {@code address} for {@code streamAllocation}. If non-null + * {@code route} is the resolved route for a connection. */ - @Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) { + void acquire(Address address, StreamAllocation streamAllocation, @Nullable Route route) { assert (Thread.holdsLock(this)); for (RealConnection connection : connections) { if (connection.isEligible(address, route)) { streamAllocation.acquire(connection, true); - return connection; + return; } } - return null; } /** diff --git a/okhttp/src/main/java/okhttp3/ConnectionSpec.java b/okhttp/src/main/java/okhttp3/ConnectionSpec.java index 004a97065bc24317a7d5a28b976b5e815cc27c1c..83efef48391795a8f2cf362d3cfd58eb9e79499a 100644 --- a/okhttp/src/main/java/okhttp3/ConnectionSpec.java +++ b/okhttp/src/main/java/okhttp3/ConnectionSpec.java @@ -17,6 +17,7 @@ package okhttp3; import java.util.Arrays; import java.util.List; +import java.util.Objects; import javax.annotation.Nullable; import javax.net.ssl.SSLSocket; import okhttp3.internal.Util; @@ -37,6 +38,12 @@ import static okhttp3.internal.Util.nonEmptyIntersection; * * <p>Use {@link Builder#allEnabledTlsVersions()} and {@link Builder#allEnabledCipherSuites} to * defer all feature selection to the underlying SSL socket. + * + * <p>The configuration of each spec changes with each OkHttp release. This is annoying: upgrading + * your OkHttp library can break connectivity to certain web servers! But it’s a necessary annoyance + * because the TLS ecosystem is dynamic and staying up to date is necessary to stay secure. See + * <a href="https://github.com/square/okhttp/wiki/TLS-Configuration-History">OkHttp's TLS + * Configuration History</a> to track these changes. */ public final class ConnectionSpec { @@ -87,24 +94,31 @@ public final class ConnectionSpec { CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, }; - /** A secure TLS connection assuming a modern client platform and server. */ + /** A secure TLS connection that requires a recent client platform and a recent server. */ public static final ConnectionSpec RESTRICTED_TLS = new Builder(true) .cipherSuites(RESTRICTED_CIPHER_SUITES) .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2) .supportsTlsExtensions(true) .build(); - /** A modern TLS connection with extensions like SNI and ALPN available. */ + /** + * A modern TLS configuration that works on most client platforms and can connect to most servers. + * This is OkHttp's default configuration. + */ public static final ConnectionSpec MODERN_TLS = new Builder(true) .cipherSuites(APPROVED_CIPHER_SUITES) - .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0) + .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2) .supportsTlsExtensions(true) .build(); - /** A backwards-compatible fallback connection for interop with obsolete servers. */ + /** + * A backwards-compatible fallback configuration that works on obsolete client platforms and can + * connect to obsolete servers. When possible, prefer to upgrade your client platform or server + * rather than using this configuration. + */ public static final ConnectionSpec COMPATIBLE_TLS = new Builder(true) .cipherSuites(APPROVED_CIPHER_SUITES) - .tlsVersions(TlsVersion.TLS_1_0) + .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0) .supportsTlsExtensions(true) .build(); @@ -247,11 +261,9 @@ public final class ConnectionSpec { return "ConnectionSpec()"; } - String cipherSuitesString = cipherSuites != null ? cipherSuites().toString() : "[all enabled]"; - String tlsVersionsString = tlsVersions != null ? tlsVersions().toString() : "[all enabled]"; return "ConnectionSpec(" - + "cipherSuites=" + cipherSuitesString - + ", tlsVersions=" + tlsVersionsString + + "cipherSuites=" + Objects.toString(cipherSuites(), "[all enabled]") + + ", tlsVersions=" + Objects.toString(tlsVersions(), "[all enabled]") + ", supportsTlsExtensions=" + supportsTlsExtensions + ")"; } @@ -328,6 +340,11 @@ public final class ConnectionSpec { return this; } + /** + * @deprecated since OkHttp 3.13 all TLS-connections are expected to support TLS extensions. + * In a future release setting this to true will be unnecessary and setting it to false will + * have no effect. + */ public Builder supportsTlsExtensions(boolean supportsTlsExtensions) { if (!tls) throw new IllegalStateException("no TLS extensions for cleartext connections"); this.supportsTlsExtensions = supportsTlsExtensions; diff --git a/okhttp/src/main/java/okhttp3/Cookie.java b/okhttp/src/main/java/okhttp3/Cookie.java index 7a4f24ba20ad9cadfa8578f906dbffb3db6fc671..867451e387f7d47188a7f3e8469ffe712b28674d 100644 --- a/okhttp/src/main/java/okhttp3/Cookie.java +++ b/okhttp/src/main/java/okhttp3/Cookie.java @@ -450,7 +450,7 @@ public final class Cookie { return cookies != null ? Collections.unmodifiableList(cookies) - : Collections.<Cookie>emptyList(); + : Collections.emptyList(); } /** diff --git a/okhttp/src/main/java/okhttp3/Credentials.java b/okhttp/src/main/java/okhttp3/Credentials.java index 530863e4a8172337cf997b34e43e94189ae36e8b..c06c1d61f030f542b336378ce2045e774bcb1983 100644 --- a/okhttp/src/main/java/okhttp3/Credentials.java +++ b/okhttp/src/main/java/okhttp3/Credentials.java @@ -18,7 +18,7 @@ package okhttp3; import java.nio.charset.Charset; import okio.ByteString; -import static okhttp3.internal.Util.ISO_8859_1; +import static java.nio.charset.StandardCharsets.ISO_8859_1; /** Factory for HTTP authorization credentials. */ public final class Credentials { diff --git a/okhttp/src/main/java/okhttp3/Dispatcher.java b/okhttp/src/main/java/okhttp3/Dispatcher.java index 3e33dff6bf59e1afe6ee6634cb9cc96d3e214b3d..d21e9df1b9769afa91b260fafe6e448f2cffd651 100644 --- a/okhttp/src/main/java/okhttp3/Dispatcher.java +++ b/okhttp/src/main/java/okhttp3/Dispatcher.java @@ -63,7 +63,7 @@ public final class Dispatcher { public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, - new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); + new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; } @@ -133,10 +133,27 @@ public final class Dispatcher { void enqueue(AsyncCall call) { synchronized (this) { readyAsyncCalls.add(call); + + // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to + // the same host. + if (!call.get().forWebSocket) { + AsyncCall existingCall = findExistingCallWithHost(call.host()); + if (existingCall != null) call.reuseCallsPerHostFrom(existingCall); + } } promoteAndExecute(); } + @Nullable private AsyncCall findExistingCallWithHost(String host) { + for (AsyncCall existingCall : runningAsyncCalls) { + if (existingCall.host().equals(host)) return existingCall; + } + for (AsyncCall existingCall : readyAsyncCalls) { + if (existingCall.host().equals(host)) return existingCall; + } + return null; + } + /** * Cancel all calls currently enqueued or executing. Includes calls executed both {@linkplain * Call#execute() synchronously} and {@linkplain Call#enqueue asynchronously}. @@ -172,9 +189,10 @@ public final class Dispatcher { AsyncCall asyncCall = i.next(); if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity. - if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity. + if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity. i.remove(); + asyncCall.callsPerHost().incrementAndGet(); executableCalls.add(asyncCall); runningAsyncCalls.add(asyncCall); } @@ -189,16 +207,6 @@ public final class Dispatcher { return isRunning; } - /** Returns the number of running calls that share a host with {@code call}. */ - private int runningCallsForHost(AsyncCall call) { - int result = 0; - for (AsyncCall c : runningAsyncCalls) { - if (c.get().forWebSocket) continue; - if (c.host().equals(call.host())) result++; - } - return result; - } - /** Used by {@code Call#execute} to signal it is in-flight. */ synchronized void executed(RealCall call) { runningSyncCalls.add(call); @@ -206,6 +214,7 @@ public final class Dispatcher { /** Used by {@code AsyncCall#run} to signal completion. */ void finished(AsyncCall call) { + call.callsPerHost().decrementAndGet(); finished(runningAsyncCalls, call); } diff --git a/okhttp/src/main/java/okhttp3/Dns.java b/okhttp/src/main/java/okhttp3/Dns.java index de3b4cb215de4182e8ccd73ec72da230890e0c03..feba0db7c9153fdebbf3ac35243c8fd7027aef3b 100644 --- a/okhttp/src/main/java/okhttp3/Dns.java +++ b/okhttp/src/main/java/okhttp3/Dns.java @@ -33,17 +33,15 @@ public interface Dns { * A DNS that uses {@link InetAddress#getAllByName} to ask the underlying operating system to * lookup IP addresses. Most custom {@link Dns} implementations should delegate to this instance. */ - Dns SYSTEM = new Dns() { - @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException { - if (hostname == null) throw new UnknownHostException("hostname == null"); - try { - return Arrays.asList(InetAddress.getAllByName(hostname)); - } catch (NullPointerException e) { - UnknownHostException unknownHostException = - new UnknownHostException("Broken system behaviour for dns lookup of " + hostname); - unknownHostException.initCause(e); - throw unknownHostException; - } + Dns SYSTEM = hostname -> { + if (hostname == null) throw new UnknownHostException("hostname == null"); + try { + return Arrays.asList(InetAddress.getAllByName(hostname)); + } catch (NullPointerException e) { + UnknownHostException unknownHostException = + new UnknownHostException("Broken system behaviour for dns lookup of " + hostname); + unknownHostException.initCause(e); + throw unknownHostException; } }; diff --git a/okhttp/src/main/java/okhttp3/EventListener.java b/okhttp/src/main/java/okhttp3/EventListener.java index fb8d4c7f4a6367fc72df8dc8b1851b9069eb9032..bdd9f050c5c4d158b714a9a0f74b34380f879407 100644 --- a/okhttp/src/main/java/okhttp3/EventListener.java +++ b/okhttp/src/main/java/okhttp3/EventListener.java @@ -53,12 +53,8 @@ public abstract class EventListener { public static final EventListener NONE = new EventListener() { }; - static EventListener.Factory factory(final EventListener listener) { - return new EventListener.Factory() { - public EventListener create(Call call) { - return listener; - } - }; + static EventListener.Factory factory(EventListener listener) { + return call -> listener; } /** diff --git a/okhttp/src/main/java/okhttp3/FormBody.java b/okhttp/src/main/java/okhttp3/FormBody.java index d3937834c3c54f1e85518d0a5875197adf4115a7..4216daccd57cf478a0315c8ee218ba95dc6710b2 100644 --- a/okhttp/src/main/java/okhttp3/FormBody.java +++ b/okhttp/src/main/java/okhttp3/FormBody.java @@ -105,13 +105,13 @@ public final class FormBody extends RequestBody { public static final class Builder { private final List<String> names = new ArrayList<>(); private final List<String> values = new ArrayList<>(); - private final Charset charset; + private final @Nullable Charset charset; public Builder() { this(null); } - public Builder(Charset charset) { + public Builder(@Nullable Charset charset) { this.charset = charset; } diff --git a/okhttp/src/main/java/okhttp3/Handshake.java b/okhttp/src/main/java/okhttp3/Handshake.java index fefe3a57288aef1ae20e57c3f48f478d9805b8fd..d2740c8f8751b1c3e51a18d736f39ed1dd7dbc5d 100644 --- a/okhttp/src/main/java/okhttp3/Handshake.java +++ b/okhttp/src/main/java/okhttp3/Handshake.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.security.Principal; import java.security.cert.Certificate; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.annotation.Nullable; @@ -68,12 +69,12 @@ public final class Handshake { } List<Certificate> peerCertificatesList = peerCertificates != null ? Util.immutableList(peerCertificates) - : Collections.<Certificate>emptyList(); + : Collections.emptyList(); Certificate[] localCertificates = session.getLocalCertificates(); List<Certificate> localCertificatesList = localCertificates != null ? Util.immutableList(localCertificates) - : Collections.<Certificate>emptyList(); + : Collections.emptyList(); return new Handshake(tlsVersion, cipherSuite, peerCertificatesList, localCertificatesList); } @@ -140,4 +141,31 @@ public final class Handshake { result = 31 * result + localCertificates.hashCode(); return result; } + + @Override public String toString() { + return "Handshake{" + + "tlsVersion=" + + tlsVersion + + " cipherSuite=" + + cipherSuite + + " peerCertificates=" + + names(peerCertificates) + + " localCertificates=" + + names(localCertificates) + + '}'; + } + + private List<String> names(List<Certificate> certificates) { + ArrayList<String> strings = new ArrayList<>(); + + for (Certificate cert : certificates) { + if (cert instanceof X509Certificate) { + strings.add(String.valueOf(((X509Certificate) cert).getSubjectDN())); + } else { + strings.add(cert.getType()); + } + } + + return strings; + } } diff --git a/okhttp/src/main/java/okhttp3/Headers.java b/okhttp/src/main/java/okhttp3/Headers.java index 8b662c96ac98adfa0e585e9c0355ddcf408748a4..06d2a96ccdbbe2177d6d5b10c80919207113a032 100644 --- a/okhttp/src/main/java/okhttp3/Headers.java +++ b/okhttp/src/main/java/okhttp3/Headers.java @@ -17,6 +17,7 @@ package okhttp3; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -30,6 +31,7 @@ import java.util.TreeSet; import javax.annotation.Nullable; import okhttp3.internal.Util; import okhttp3.internal.http.HttpDate; +import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; /** * The header fields of a single HTTP message. Values are uninterpreted strings; use {@code Request} @@ -72,6 +74,16 @@ public final class Headers { return value != null ? HttpDate.parse(value) : null; } + /** + * Returns the last value corresponding to the specified field parsed as an HTTP date, or null if + * either the field is absent or cannot be parsed as a date. + */ + @IgnoreJRERequirement + public @Nullable Instant getInstant(String name) { + Date value = getDate(name); + return value != null ? value.toInstant() : null; + } + /** Returns the number of field values. */ public int size() { return namesAndValues.length / 2; @@ -107,7 +119,7 @@ public final class Headers { } return result != null ? Collections.unmodifiableList(result) - : Collections.<String>emptyList(); + : Collections.emptyList(); } /** @@ -190,7 +202,7 @@ public final class Headers { return result; } - private static String get(String[] namesAndValues, String name) { + private static @Nullable String get(String[] namesAndValues, String name) { for (int i = namesAndValues.length - 2; i >= 0; i -= 2) { if (name.equalsIgnoreCase(namesAndValues[i])) { return namesAndValues[i + 1]; @@ -326,8 +338,7 @@ public final class Headers { * Adds all headers from an existing collection. */ public Builder addAll(Headers headers) { - int size = headers.size(); - for (int i = 0; i < size; i++) { + for (int i = 0, size = headers.size(); i < size; i++) { addLenient(headers.name(i), headers.value(i)); } @@ -335,8 +346,8 @@ public final class Headers { } /** - * Add a header with the specified name and formatted Date. - * Does validation of header names and values. + * Add a header with the specified name and formatted date. Does validation of header names and + * value. */ public Builder add(String name, Date value) { if (value == null) throw new NullPointerException("value for name " + name + " == null"); @@ -344,6 +355,16 @@ public final class Headers { return this; } + /** + * Add a header with the specified name and formatted instant. Does validation of header names + * and value. + */ + @IgnoreJRERequirement + public Builder add(String name, Instant value) { + if (value == null) throw new NullPointerException("value for name " + name + " == null"); + return add(name, new Date(value.toEpochMilli())); + } + /** * Set a field with the specified date. If the field is not found, it is added. If the field is * found, the existing values are replaced. @@ -354,6 +375,16 @@ public final class Headers { return this; } + /** + * Set a field with the specified instant. If the field is not found, it is added. If the field + * is found, the existing values are replaced. + */ + @IgnoreJRERequirement + public Builder set(String name, Instant value) { + if (value == null) throw new NullPointerException("value for name " + name + " == null"); + return set(name, new Date(value.toEpochMilli())); + } + /** * Add a field with the specified value without any validation. Only appropriate for headers * from the remote peer or cache. @@ -388,7 +419,7 @@ public final class Headers { } /** Equivalent to {@code build().get(name)}, but potentially faster. */ - public String get(String name) { + public @Nullable String get(String name) { for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) { if (name.equalsIgnoreCase(namesAndValues.get(i))) { return namesAndValues.get(i + 1); diff --git a/okhttp/src/main/java/okhttp3/HttpUrl.java b/okhttp/src/main/java/okhttp3/HttpUrl.java index 9ca007cefd37b69c6c552a46236275c0bc46dfc4..26125e306deebbbb28a83e2deefe8234abe35462 100644 --- a/okhttp/src/main/java/okhttp3/HttpUrl.java +++ b/okhttp/src/main/java/okhttp3/HttpUrl.java @@ -31,6 +31,7 @@ import okhttp3.internal.Util; import okhttp3.internal.publicsuffix.PublicSuffixDatabase; import okio.Buffer; +import static java.nio.charset.StandardCharsets.UTF_8; import static okhttp3.internal.Util.decodeHexDigit; import static okhttp3.internal.Util.delimiterOffset; import static okhttp3.internal.Util.skipLeadingAsciiWhitespace; @@ -1087,9 +1088,8 @@ public final class HttpUrl { public Builder setPathSegment(int index, String pathSegment) { if (pathSegment == null) throw new NullPointerException("pathSegment == null"); - String canonicalPathSegment = canonicalize( - pathSegment, 0, pathSegment.length(), PATH_SEGMENT_ENCODE_SET, false, false, false, true, - null); + String canonicalPathSegment = canonicalize(pathSegment, 0, pathSegment.length(), + PATH_SEGMENT_ENCODE_SET, false, false, false, true, null); if (isDot(canonicalPathSegment) || isDotDot(canonicalPathSegment)) { throw new IllegalArgumentException("unexpected path segment: " + pathSegment); } @@ -1101,9 +1101,8 @@ public final class HttpUrl { if (encodedPathSegment == null) { throw new NullPointerException("encodedPathSegment == null"); } - String canonicalPathSegment = canonicalize(encodedPathSegment, - 0, encodedPathSegment.length(), PATH_SEGMENT_ENCODE_SET, true, false, false, true, - null); + String canonicalPathSegment = canonicalize(encodedPathSegment, 0, encodedPathSegment.length(), + PATH_SEGMENT_ENCODE_SET, true, false, false, true, null); encodedPathSegments.set(index, canonicalPathSegment); if (isDot(canonicalPathSegment) || isDotDot(canonicalPathSegment)) { throw new IllegalArgumentException("unexpected path segment: " + encodedPathSegment); @@ -1361,9 +1360,8 @@ public final class HttpUrl { if (!hasPassword) { int passwordColonOffset = delimiterOffset( input, pos, componentDelimiterOffset, ':'); - String canonicalUsername = canonicalize( - input, pos, passwordColonOffset, USERNAME_ENCODE_SET, true, false, false, true, - null); + String canonicalUsername = canonicalize(input, pos, passwordColonOffset, + USERNAME_ENCODE_SET, true, false, false, true, null); this.encodedUsername = hasUsername ? this.encodedUsername + "%40" + canonicalUsername : canonicalUsername; @@ -1376,8 +1374,7 @@ public final class HttpUrl { hasUsername = true; } else { this.encodedPassword = this.encodedPassword + "%40" + canonicalize(input, pos, - componentDelimiterOffset, PASSWORD_ENCODE_SET, true, false, false, true, - null); + componentDelimiterOffset, PASSWORD_ENCODE_SET, true, false, false, true, null); } pos = componentDelimiterOffset + 1; break; @@ -1585,7 +1582,7 @@ public final class HttpUrl { return limit; // No colon. } - private static String canonicalizeHost(String input, int pos, int limit) { + private static @Nullable String canonicalizeHost(String input, int pos, int limit) { // Start by percent decoding the host. The WHATWG spec suggests doing this only after we've // checked for IPv6 square braces. But Chrome does it first, and that's more lenient. String percentDecoded = percentDecode(input, pos, limit, false); @@ -1681,7 +1678,7 @@ public final class HttpUrl { */ static String canonicalize(String input, int pos, int limit, String encodeSet, boolean alreadyEncoded, boolean strict, boolean plusIsSpace, boolean asciiOnly, - Charset charset) { + @Nullable Charset charset) { int codePoint; for (int i = pos; i < limit; i += Character.charCount(codePoint)) { codePoint = input.codePointAt(i); @@ -1706,7 +1703,7 @@ public final class HttpUrl { static void canonicalize(Buffer out, String input, int pos, int limit, String encodeSet, boolean alreadyEncoded, boolean strict, boolean plusIsSpace, boolean asciiOnly, - Charset charset) { + @Nullable Charset charset) { Buffer encodedCharBuffer = null; // Lazily allocated. int codePoint; for (int i = pos; i < limit; i += Character.charCount(codePoint)) { @@ -1727,7 +1724,7 @@ public final class HttpUrl { encodedCharBuffer = new Buffer(); } - if (charset == null || charset.equals(Util.UTF_8)) { + if (charset == null || charset.equals(UTF_8)) { encodedCharBuffer.writeUtf8CodePoint(codePoint); } else { encodedCharBuffer.writeString(input, i, i + Character.charCount(codePoint), charset); @@ -1747,10 +1744,9 @@ public final class HttpUrl { } static String canonicalize(String input, String encodeSet, boolean alreadyEncoded, boolean strict, - boolean plusIsSpace, boolean asciiOnly, Charset charset) { - return canonicalize( - input, 0, input.length(), encodeSet, alreadyEncoded, strict, plusIsSpace, asciiOnly, - charset); + boolean plusIsSpace, boolean asciiOnly, @Nullable Charset charset) { + return canonicalize(input, 0, input.length(), encodeSet, alreadyEncoded, strict, plusIsSpace, + asciiOnly, charset); } static String canonicalize(String input, String encodeSet, boolean alreadyEncoded, boolean strict, diff --git a/okhttp/src/main/java/okhttp3/MultipartBody.java b/okhttp/src/main/java/okhttp3/MultipartBody.java index 800064cfb5e072ca36ae0e1b0a8d4c5dfb7c4cf2..a3f4863fba4e8122ee608ed4ca73a0205a7121e9 100644 --- a/okhttp/src/main/java/okhttp3/MultipartBody.java +++ b/okhttp/src/main/java/okhttp3/MultipartBody.java @@ -199,7 +199,7 @@ public final class MultipartBody extends RequestBody { * want to have a good chance of things working, please avoid double-quotes, newlines, percent * signs, and the like in your field names. */ - static StringBuilder appendQuotedString(StringBuilder target, String key) { + static void appendQuotedString(StringBuilder target, String key) { target.append('"'); for (int i = 0, len = key.length(); i < len; i++) { char ch = key.charAt(i); @@ -219,7 +219,6 @@ public final class MultipartBody extends RequestBody { } } target.append('"'); - return target; } public static final class Part { @@ -256,7 +255,11 @@ public final class MultipartBody extends RequestBody { appendQuotedString(disposition, filename); } - return create(Headers.of("Content-Disposition", disposition.toString()), body); + Headers headers = new Headers.Builder() + .addUnsafeNonAscii("Content-Disposition", disposition.toString()) + .build(); + + return create(headers, body); } final @Nullable Headers headers; diff --git a/okhttp/src/main/java/okhttp3/OkHttpClient.java b/okhttp/src/main/java/okhttp3/OkHttpClient.java index 1686af2aa969981cf6f5a176065ac2254227c321..433982e13644bf017b30d05acc9691684836a31e 100644 --- a/okhttp/src/main/java/okhttp3/OkHttpClient.java +++ b/okhttp/src/main/java/okhttp3/OkHttpClient.java @@ -41,6 +41,7 @@ import okhttp3.internal.cache.InternalCache; import okhttp3.internal.connection.RealConnection; import okhttp3.internal.connection.RouteDatabase; import okhttp3.internal.connection.StreamAllocation; +import okhttp3.internal.http.HttpCodec; import okhttp3.internal.platform.Platform; import okhttp3.internal.proxy.NullProxySelector; import okhttp3.internal.tls.CertificateChainCleaner; @@ -50,7 +51,6 @@ import okio.Sink; import okio.Source; import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; -import static okhttp3.internal.Util.assertionError; import static okhttp3.internal.Util.checkDuration; /** @@ -149,16 +149,16 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory return pool.connectionBecameIdle(connection); } - @Override public RealConnection get(ConnectionPool pool, Address address, - StreamAllocation streamAllocation, Route route) { - return pool.get(address, streamAllocation, route); + @Override public void acquire(ConnectionPool pool, Address address, + StreamAllocation streamAllocation, @Nullable Route route) { + pool.acquire(address, streamAllocation, route); } @Override public boolean equalsNonHost(Address a, Address b) { return a.equalsNonHost(b); } - @Override public Socket deduplicate( + @Override public @Nullable Socket deduplicate( ConnectionPool pool, Address address, StreamAllocation streamAllocation) { return pool.deduplicate(address, streamAllocation); } @@ -195,6 +195,10 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory @Override public Call newWebSocketCall(OkHttpClient client, Request originalRequest) { return RealCall.newRealCall(client, originalRequest, true); } + + @Override public void initCodec(Response.Builder responseBuilder, HttpCodec httpCodec) { + responseBuilder.initCodec(httpCodec); + } }; } @@ -293,31 +297,34 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory sslContext.init(null, new TrustManager[] { trustManager }, null); return sslContext.getSocketFactory(); } catch (GeneralSecurityException e) { - throw assertionError("No System TLS", e); // The system has no TLS. Just give up. + throw new AssertionError("No System TLS", e); // The system has no TLS. Just give up. } } - /** Default call timeout (in milliseconds). */ + /** + * Default call timeout (in milliseconds). By default there is no timeout for complete calls, but + * there is for the connect, write, and read actions within a call. + */ public int callTimeoutMillis() { return callTimeout; } - /** Default connect timeout (in milliseconds). */ + /** Default connect timeout (in milliseconds). The default is 10 seconds. */ public int connectTimeoutMillis() { return connectTimeout; } - /** Default read timeout (in milliseconds). */ + /** Default read timeout (in milliseconds). The default is 10 seconds. */ public int readTimeoutMillis() { return readTimeout; } - /** Default write timeout (in milliseconds). */ + /** Default write timeout (in milliseconds). The default is 10 seconds. */ public int writeTimeoutMillis() { return writeTimeout; } - /** Web socket ping interval (in milliseconds). */ + /** Web socket and HTTP/2 ping interval (in milliseconds). By default pings are not sent. */ public int pingIntervalMillis() { return pingInterval; } @@ -338,7 +345,7 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory return cache; } - InternalCache internalCache() { + @Nullable InternalCache internalCache() { return cache != null ? cache.internalCache : internalCache; } @@ -535,6 +542,8 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory * <p>The call timeout spans the entire call: resolving DNS, connecting, writing the request * body, server processing, and reading the response body. If the call requires redirects or * retries all must complete within one timeout period. + * + * <p>The default value is 0 which imposes no timeout. */ public Builder callTimeout(long timeout, TimeUnit unit) { callTimeout = checkDuration("timeout", timeout, unit); @@ -548,6 +557,8 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory * <p>The call timeout spans the entire call: resolving DNS, connecting, writing the request * body, server processing, and reading the response body. If the call requires redirects or * retries all must complete within one timeout period. + * + * <p>The default value is 0 which imposes no timeout. */ @IgnoreJRERequirement public Builder callTimeout(Duration duration) { @@ -749,6 +760,9 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory */ public Builder socketFactory(SocketFactory socketFactory) { if (socketFactory == null) throw new NullPointerException("socketFactory == null"); + if (socketFactory instanceof SSLSocketFactory) { + throw new IllegalArgumentException("socketFactory instanceof SSLSocketFactory"); + } this.socketFactory = socketFactory; return this; } diff --git a/okhttp/src/main/java/okhttp3/RealCall.java b/okhttp/src/main/java/okhttp3/RealCall.java index a9ba5be366b43fcae33f3a9bf91db4feeb42ac8d..92201fe8d6901ad189f35e61692cb6fb150d83c6 100644 --- a/okhttp/src/main/java/okhttp3/RealCall.java +++ b/okhttp/src/main/java/okhttp3/RealCall.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.Nullable; import okhttp3.internal.NamedRunnable; import okhttp3.internal.cache.CacheInterceptor; @@ -59,7 +60,7 @@ final class RealCall implements Call { this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; - this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); + this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client); this.timeout = new AsyncTimeout() { @Override protected void timedOut() { cancel(); @@ -153,12 +154,21 @@ final class RealCall implements Call { final class AsyncCall extends NamedRunnable { private final Callback responseCallback; + private volatile AtomicInteger callsPerHost = new AtomicInteger(0); AsyncCall(Callback responseCallback) { super("OkHttp %s", redactedUrl()); this.responseCallback = responseCallback; } + AtomicInteger callsPerHost() { + return callsPerHost; + } + + void reuseCallsPerHostFrom(AsyncCall other) { + this.callsPerHost = other.callsPerHost; + } + String host() { return originalRequest.url().host(); } diff --git a/okhttp/src/main/java/okhttp3/Request.java b/okhttp/src/main/java/okhttp3/Request.java index df0bebd021a609b37ff05d808ce44b4679bb78d5..9bbdc86407c9ca0237dd5a8e202c10005fecb974 100644 --- a/okhttp/src/main/java/okhttp3/Request.java +++ b/okhttp/src/main/java/okhttp3/Request.java @@ -135,7 +135,7 @@ public final class Request { this.method = request.method; this.body = request.body; this.tags = request.tags.isEmpty() - ? Collections.<Class<?>, Object>emptyMap() + ? Collections.emptyMap() : new LinkedHashMap<>(request.tags); this.headers = request.headers.newBuilder(); } diff --git a/okhttp/src/main/java/okhttp3/RequestBody.java b/okhttp/src/main/java/okhttp3/RequestBody.java index baf33ce6c65349efe077eaf93436e33faf0cce60..c0279f30811380df0ebbd8fa1cff5e0199fc4dee 100644 --- a/okhttp/src/main/java/okhttp3/RequestBody.java +++ b/okhttp/src/main/java/okhttp3/RequestBody.java @@ -25,6 +25,8 @@ import okio.ByteString; import okio.Okio; import okio.Source; +import static java.nio.charset.StandardCharsets.UTF_8; + public abstract class RequestBody { /** Returns the Content-Type header for this body. */ public abstract @Nullable MediaType contentType(); @@ -45,11 +47,11 @@ public abstract class RequestBody { * and lacks a charset, this will use UTF-8. */ public static RequestBody create(@Nullable MediaType contentType, String content) { - Charset charset = Util.UTF_8; + Charset charset = UTF_8; if (contentType != null) { charset = contentType.charset(); if (charset == null) { - charset = Util.UTF_8; + charset = UTF_8; contentType = MediaType.parse(contentType + "; charset=utf-8"); } } @@ -114,12 +116,8 @@ public abstract class RequestBody { } @Override public void writeTo(BufferedSink sink) throws IOException { - Source source = null; - try { - source = Okio.source(file); + try (Source source = Okio.source(file)) { sink.writeAll(source); - } finally { - Util.closeQuietly(source); } } }; diff --git a/okhttp/src/main/java/okhttp3/Response.java b/okhttp/src/main/java/okhttp3/Response.java index b9ec3993806c7fdd83cbc60a0c7fab273005bf06..b58c9aac217e0c402d4ec87958eb66eb84837969 100644 --- a/okhttp/src/main/java/okhttp3/Response.java +++ b/okhttp/src/main/java/okhttp3/Response.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.Collections; import java.util.List; import javax.annotation.Nullable; +import okhttp3.internal.http.HttpCodec; import okhttp3.internal.http.HttpHeaders; import okio.Buffer; import okio.BufferedSource; @@ -53,6 +54,7 @@ public final class Response implements Closeable { final @Nullable Response priorResponse; final long sentRequestAtMillis; final long receivedResponseAtMillis; + final @Nullable HttpCodec httpCodec; private volatile @Nullable CacheControl cacheControl; // Lazily initialized. @@ -69,6 +71,7 @@ public final class Response implements Closeable { this.priorResponse = builder.priorResponse; this.sentRequestAtMillis = builder.sentRequestAtMillis; this.receivedResponseAtMillis = builder.receivedResponseAtMillis; + this.httpCodec = builder.httpCodec; } /** @@ -136,6 +139,14 @@ public final class Response implements Closeable { return headers; } + /** + * Returns the trailers after the HTTP response, which may be empty. It is an error to call this + * before the entire HTTP response body has been consumed. + */ + public Headers trailers() throws IOException { + return httpCodec.trailers(); + } + /** * Peeks up to {@code byteCount} bytes from the response body and returns them as a new response * body. If fewer than {@code byteCount} bytes are in the response body, the full response body is @@ -148,21 +159,11 @@ public final class Response implements Closeable { * applications should set a modest limit on {@code byteCount}, such as 1 MiB. */ public ResponseBody peekBody(long byteCount) throws IOException { - BufferedSource source = body.source(); - source.request(byteCount); - Buffer copy = source.buffer().clone(); - - // There may be more than byteCount bytes in source.buffer(). If there is, return a prefix. - Buffer result; - if (copy.size() > byteCount) { - result = new Buffer(); - result.write(copy, byteCount); - copy.clear(); - } else { - result = copy; - } - - return ResponseBody.create(body.contentType(), result.size(), result); + BufferedSource peeked = body.source().peek(); + Buffer buffer = new Buffer(); + peeked.request(byteCount); + buffer.write(peeked, Math.min(byteCount, peeked.getBuffer().size())); + return ResponseBody.create(body.contentType(), buffer.size(), buffer); } /** @@ -313,6 +314,7 @@ public final class Response implements Closeable { @Nullable Response priorResponse; long sentRequestAtMillis; long receivedResponseAtMillis; + @Nullable HttpCodec httpCodec; public Builder() { headers = new Headers.Builder(); @@ -331,6 +333,7 @@ public final class Response implements Closeable { this.priorResponse = response.priorResponse; this.sentRequestAtMillis = response.sentRequestAtMillis; this.receivedResponseAtMillis = response.receivedResponseAtMillis; + this.httpCodec = response.httpCodec; } public Builder request(Request request) { @@ -438,6 +441,10 @@ public final class Response implements Closeable { return this; } + void initCodec(HttpCodec httpCodec) { + this.httpCodec = httpCodec; + } + public Response build() { if (request == null) throw new IllegalStateException("request == null"); if (protocol == null) throw new IllegalStateException("protocol == null"); diff --git a/okhttp/src/main/java/okhttp3/ResponseBody.java b/okhttp/src/main/java/okhttp3/ResponseBody.java index 9b06b6ea8f772744ed58a278ff666211ba6ce219..96b7e38a0f42b63a3f3de859adc0d162dfa82f26 100644 --- a/okhttp/src/main/java/okhttp3/ResponseBody.java +++ b/okhttp/src/main/java/okhttp3/ResponseBody.java @@ -27,7 +27,7 @@ import okio.Buffer; import okio.BufferedSource; import okio.ByteString; -import static okhttp3.internal.Util.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; /** * A one-shot stream from the origin server to the client application with the raw bytes of the @@ -131,12 +131,9 @@ public abstract class ResponseBody implements Closeable { throw new IOException("Cannot buffer entire body for content length: " + contentLength); } - BufferedSource source = source(); byte[] bytes; - try { + try (BufferedSource source = source()) { bytes = source.readByteArray(); - } finally { - Util.closeQuietly(source); } if (contentLength != -1 && contentLength != bytes.length) { throw new IOException("Content-Length (" @@ -149,10 +146,15 @@ public abstract class ResponseBody implements Closeable { } /** - * Returns the response as a character stream decoded with the charset of the Content-Type header. - * If that header is either absent or lacks a charset, this will attempt to decode the response - * body in accordance to <a href="https://en.wikipedia.org/wiki/Byte_order_mark">its BOM</a> or - * UTF-8. + * Returns the response as a character stream. + * + * <p>If the response starts with a <a href="https://en.wikipedia.org/wiki/Byte_order_mark">Byte + * Order Mark (BOM)</a>, it is consumed and used to determine the charset of the response bytes. + * + * <p>Otherwise if the response has a Content-Type header that specifies a charset, that is used + * to determine the charset of the response bytes. + * + * <p>Otherwise the response bytes are decoded as UTF-8. */ public final Reader charStream() { Reader r = reader; @@ -160,22 +162,24 @@ public abstract class ResponseBody implements Closeable { } /** - * Returns the response as a string decoded with the charset of the Content-Type header. If that - * header is either absent or lacks a charset, this will attempt to decode the response body in - * accordance to <a href="https://en.wikipedia.org/wiki/Byte_order_mark">its BOM</a> or UTF-8. - * Closes {@link ResponseBody} automatically. + * Returns the response as a string. + * + * <p>If the response starts with a <a href="https://en.wikipedia.org/wiki/Byte_order_mark">Byte + * Order Mark (BOM)</a>, it is consumed and used to determine the charset of the response bytes. + * + * <p>Otherwise if the response has a Content-Type header that specifies a charset, that is used + * to determine the charset of the response bytes. + * + * <p>Otherwise the response bytes are decoded as UTF-8. * * <p>This method loads entire response body into memory. If the response body is very large this * may trigger an {@link OutOfMemoryError}. Prefer to stream the response body if this is a * possibility for your response. */ public final String string() throws IOException { - BufferedSource source = source(); - try { + try (BufferedSource source = source()) { Charset charset = Util.bomAwareCharset(source, charset()); return source.readString(charset); - } finally { - Util.closeQuietly(source); } } diff --git a/okhttp/src/main/java/okhttp3/internal/Internal.java b/okhttp/src/main/java/okhttp3/internal/Internal.java index 62fcfaa732746b7fad2bf26bc03a95843e9a62f9..ac4d607eb42e2538ba7b5fd0ab1459fb7973a9ce 100644 --- a/okhttp/src/main/java/okhttp3/internal/Internal.java +++ b/okhttp/src/main/java/okhttp3/internal/Internal.java @@ -32,6 +32,7 @@ import okhttp3.internal.cache.InternalCache; import okhttp3.internal.connection.RealConnection; import okhttp3.internal.connection.RouteDatabase; import okhttp3.internal.connection.StreamAllocation; +import okhttp3.internal.http.HttpCodec; /** * Escalate internal APIs in {@code okhttp3} so they can be used from OkHttp's implementation @@ -52,12 +53,12 @@ public abstract class Internal { public abstract void setCache(OkHttpClient.Builder builder, InternalCache internalCache); - public abstract RealConnection get(ConnectionPool pool, Address address, - StreamAllocation streamAllocation, Route route); + public abstract void acquire(ConnectionPool pool, Address address, + StreamAllocation streamAllocation, @Nullable Route route); public abstract boolean equalsNonHost(Address a, Address b); - public abstract Socket deduplicate( + public abstract @Nullable Socket deduplicate( ConnectionPool pool, Address address, StreamAllocation streamAllocation); public abstract void put(ConnectionPool pool, RealConnection connection); @@ -78,4 +79,7 @@ public abstract class Internal { public abstract @Nullable IOException timeoutExit(Call call, @Nullable IOException e); public abstract Call newWebSocketCall(OkHttpClient client, Request request); + + public abstract void initCodec( + Response.Builder responseBuilder, HttpCodec httpCodec); } diff --git a/okhttp/src/main/java/okhttp3/internal/Util.java b/okhttp/src/main/java/okhttp3/internal/Util.java index 66e559cc18b6eae9cf326f2866e92083a6264935..5315a6b6856ce69752c2addb5e386c75680dcfc5 100644 --- a/okhttp/src/main/java/okhttp3/internal/Util.java +++ b/okhttp/src/main/java/okhttp3/internal/Util.java @@ -26,6 +26,7 @@ import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.nio.charset.Charset; +import java.security.AccessControlException; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.util.ArrayList; @@ -52,37 +53,38 @@ import okhttp3.internal.http2.Header; import okio.Buffer; import okio.BufferedSource; import okio.ByteString; +import okio.Options; import okio.Source; +import static java.nio.charset.StandardCharsets.UTF_16BE; +import static java.nio.charset.StandardCharsets.UTF_16LE; +import static java.nio.charset.StandardCharsets.UTF_8; + /** Junk drawer of utility methods. */ public final class Util { public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; public static final String[] EMPTY_STRING_ARRAY = new String[0]; + public static final Headers EMPTY_HEADERS = Headers.of(); public static final ResponseBody EMPTY_RESPONSE = ResponseBody.create(null, EMPTY_BYTE_ARRAY); public static final RequestBody EMPTY_REQUEST = RequestBody.create(null, EMPTY_BYTE_ARRAY); - private static final ByteString UTF_8_BOM = ByteString.decodeHex("efbbbf"); - private static final ByteString UTF_16_BE_BOM = ByteString.decodeHex("feff"); - private static final ByteString UTF_16_LE_BOM = ByteString.decodeHex("fffe"); - private static final ByteString UTF_32_BE_BOM = ByteString.decodeHex("0000ffff"); - private static final ByteString UTF_32_LE_BOM = ByteString.decodeHex("ffff0000"); + /** Byte order marks. */ + private static final Options UNICODE_BOMS = Options.of( + ByteString.decodeHex("efbbbf"), // UTF-8 + ByteString.decodeHex("feff"), // UTF-16BE + ByteString.decodeHex("fffe"), // UTF-16LE + ByteString.decodeHex("0000ffff"), // UTF-32BE + ByteString.decodeHex("ffff0000") // UTF-32LE + ); - public static final Charset UTF_8 = Charset.forName("UTF-8"); - public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); - private static final Charset UTF_16_BE = Charset.forName("UTF-16BE"); - private static final Charset UTF_16_LE = Charset.forName("UTF-16LE"); - private static final Charset UTF_32_BE = Charset.forName("UTF-32BE"); - private static final Charset UTF_32_LE = Charset.forName("UTF-32LE"); + private static final Charset UTF_32BE = Charset.forName("UTF-32BE"); + private static final Charset UTF_32LE = Charset.forName("UTF-32LE"); /** GMT and UTC are equivalent for our purposes. */ public static final TimeZone UTC = TimeZone.getTimeZone("GMT"); - public static final Comparator<String> NATURAL_ORDER = new Comparator<String>() { - @Override public int compare(String a, String b) { - return a.compareTo(b); - } - }; + public static final Comparator<String> NATURAL_ORDER = String::compareTo; private static final Method addSuppressedExceptionMethod; @@ -127,11 +129,6 @@ public final class Util { } } - /** Returns true if two possibly-null objects are equal. */ - public static boolean equal(Object a, Object b) { - return a == b || (a != null && a.equals(b)); - } - /** * Closes {@code closeable}, ignoring any checked exceptions. Does nothing if {@code closeable} is * null. @@ -227,22 +224,21 @@ public final class Util { /** Returns an immutable copy of {@code map}. */ public static <K, V> Map<K, V> immutableMap(Map<K, V> map) { return map.isEmpty() - ? Collections.<K, V>emptyMap() + ? Collections.emptyMap() : Collections.unmodifiableMap(new LinkedHashMap<>(map)); } /** Returns an immutable list containing {@code elements}. */ + @SafeVarargs public static <T> List<T> immutableList(T... elements) { return Collections.unmodifiableList(Arrays.asList(elements.clone())); } - public static ThreadFactory threadFactory(final String name, final boolean daemon) { - return new ThreadFactory() { - @Override public Thread newThread(Runnable runnable) { - Thread result = new Thread(runnable, name); - result.setDaemon(daemon); - return result; - } + public static ThreadFactory threadFactory(String name, boolean daemon) { + return runnable -> { + Thread result = new Thread(runnable, name); + result.setDaemon(daemon); + return result; }; } @@ -250,7 +246,6 @@ public final class Util { * Returns an array containing only elements found in {@code first} and also in {@code * second}. The returned elements are in the same order as in {@code first}. */ - @SuppressWarnings("unchecked") public static String[] intersect( Comparator<? super String> comparator, String[] first, String[] second) { List<String> result = new ArrayList<>(); @@ -405,6 +400,7 @@ public final class Util { if (inetAddress == null) return null; byte[] address = inetAddress.getAddress(); if (address.length == 16) return inet6AddressToAscii(address); + if (address.length == 4) return inetAddress.getHostAddress(); // An IPv4-mapped IPv6 address. throw new AssertionError("Invalid IPv6 address: '" + host + "'"); } @@ -468,27 +464,15 @@ public final class Util { } public static Charset bomAwareCharset(BufferedSource source, Charset charset) throws IOException { - if (source.rangeEquals(0, UTF_8_BOM)) { - source.skip(UTF_8_BOM.size()); - return UTF_8; + switch (source.select(UNICODE_BOMS)) { + case 0: return UTF_8; + case 1: return UTF_16BE; + case 2: return UTF_16LE; + case 3: return UTF_32BE; + case 4: return UTF_32LE; + case -1: return charset; + default: throw new AssertionError(); } - if (source.rangeEquals(0, UTF_16_BE_BOM)) { - source.skip(UTF_16_BE_BOM.size()); - return UTF_16_BE; - } - if (source.rangeEquals(0, UTF_16_LE_BOM)) { - source.skip(UTF_16_LE_BOM.size()); - return UTF_16_LE; - } - if (source.rangeEquals(0, UTF_32_BE_BOM)) { - source.skip(UTF_32_BE_BOM.size()); - return UTF_32_BE; - } - if (source.rangeEquals(0, UTF_32_LE_BOM)) { - source.skip(UTF_32_LE_BOM.size()); - return UTF_32_LE; - } - return charset; } public static int checkDuration(String name, long duration, TimeUnit unit) { @@ -500,16 +484,6 @@ public final class Util { return (int) millis; } - public static AssertionError assertionError(String message, Exception e) { - AssertionError assertionError = new AssertionError(message); - try { - assertionError.initCause(e); - } catch (IllegalStateException ise) { - // ignored, shouldn't happen - } - return assertionError; - } - public static int decodeHexDigit(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; @@ -671,7 +645,7 @@ public final class Util { } return (X509TrustManager) trustManagers[0]; } catch (GeneralSecurityException e) { - throw assertionError("No System TLS", e); // The system has no TLS. Just give up. + throw new AssertionError("No System TLS", e); // The system has no TLS. Just give up. } } @@ -682,4 +656,26 @@ public final class Util { } return builder.build(); } + + public static List<Header> toHeaderBlock(Headers headers) { + List<Header> result = new ArrayList<>(); + for (int i = 0; i < headers.size(); i++) { + result.add(new Header(headers.name(i), headers.value(i))); + } + return result; + } + + /** + * Returns the system property, or defaultValue if the system property is null or + * cannot be read (e.g. because of security policy restrictions). + */ + public static String getSystemProperty(String key, @Nullable String defaultValue) { + String value; + try { + value = System.getProperty(key); + } catch (AccessControlException ex) { + return defaultValue; + } + return value != null ? value : defaultValue; + } } diff --git a/okhttp/src/main/java/okhttp3/internal/cache/CacheInterceptor.java b/okhttp/src/main/java/okhttp3/internal/cache/CacheInterceptor.java index d46494d95b9ea019b04762c12e62599e915e6b30..c00e3b0cff65cf13e21ade6eea5797b70cf6e1b4 100644 --- a/okhttp/src/main/java/okhttp3/internal/cache/CacheInterceptor.java +++ b/okhttp/src/main/java/okhttp3/internal/cache/CacheInterceptor.java @@ -17,6 +17,7 @@ package okhttp3.internal.cache; import java.io.IOException; +import javax.annotation.Nullable; import okhttp3.Headers; import okhttp3.Interceptor; import okhttp3.Protocol; @@ -43,9 +44,9 @@ import static okhttp3.internal.Util.discard; /** Serves requests from the cache and writes responses to the cache. */ public final class CacheInterceptor implements Interceptor { - final InternalCache cache; + final @Nullable InternalCache cache; - public CacheInterceptor(InternalCache cache) { + public CacheInterceptor(@Nullable InternalCache cache) { this.cache = cache; } @@ -224,8 +225,9 @@ public final class CacheInterceptor implements Interceptor { if ("Warning".equalsIgnoreCase(fieldName) && value.startsWith("1")) { continue; // Drop 100-level freshness warnings. } - if (isContentSpecificHeader(fieldName) || !isEndToEnd(fieldName) - || networkHeaders.get(fieldName) == null) { + if (isContentSpecificHeader(fieldName) + || !isEndToEnd(fieldName) + || networkHeaders.get(fieldName) == null) { Internal.instance.addLenient(result, fieldName, value); } } diff --git a/okhttp/src/main/java/okhttp3/internal/cache/DiskLruCache.java b/okhttp/src/main/java/okhttp3/internal/cache/DiskLruCache.java index c3fb740e28227fe60f46ee08b8ed39483ebe2af9..3b193f3b91f093fa300665d45f02b582b055b3c1 100644 --- a/okhttp/src/main/java/okhttp3/internal/cache/DiskLruCache.java +++ b/okhttp/src/main/java/okhttp3/internal/cache/DiskLruCache.java @@ -267,14 +267,13 @@ public final class DiskLruCache implements Closeable, Flushable { // Use a single background thread to evict entries. Executor executor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, - new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp DiskLruCache", true)); + new LinkedBlockingQueue<>(), Util.threadFactory("OkHttp DiskLruCache", true)); return new DiskLruCache(fileSystem, directory, appVersion, valueCount, maxSize, executor); } private void readJournal() throws IOException { - BufferedSource source = Okio.buffer(fileSystem.source(journalFile)); - try { + try (BufferedSource source = Okio.buffer(fileSystem.source(journalFile))) { String magic = source.readUtf8LineStrict(); String version = source.readUtf8LineStrict(); String appVersionString = source.readUtf8LineStrict(); @@ -306,8 +305,6 @@ public final class DiskLruCache implements Closeable, Flushable { } else { journalWriter = newJournalWriter(); } - } finally { - Util.closeQuietly(source); } } @@ -393,8 +390,7 @@ public final class DiskLruCache implements Closeable, Flushable { journalWriter.close(); } - BufferedSink writer = Okio.buffer(fileSystem.sink(journalFileTmp)); - try { + try (BufferedSink writer = Okio.buffer(fileSystem.sink(journalFileTmp))) { writer.writeUtf8(MAGIC).writeByte('\n'); writer.writeUtf8(VERSION_1).writeByte('\n'); writer.writeDecimalLong(appVersion).writeByte('\n'); @@ -413,8 +409,6 @@ public final class DiskLruCache implements Closeable, Flushable { writer.writeByte('\n'); } } - } finally { - writer.close(); } if (fileSystem.exists(journalFile)) { diff --git a/okhttp/src/main/java/okhttp3/internal/cache/InternalCache.java b/okhttp/src/main/java/okhttp3/internal/cache/InternalCache.java index 6c70465b596e88c75ce4d6e1dcdfffb5c7c7f84a..abb953472d44d5e380fb1799799f76338b34163c 100644 --- a/okhttp/src/main/java/okhttp3/internal/cache/InternalCache.java +++ b/okhttp/src/main/java/okhttp3/internal/cache/InternalCache.java @@ -16,6 +16,7 @@ package okhttp3.internal.cache; import java.io.IOException; +import javax.annotation.Nullable; import okhttp3.Request; import okhttp3.Response; @@ -24,9 +25,9 @@ import okhttp3.Response; * okhttp3.Cache}. */ public interface InternalCache { - Response get(Request request) throws IOException; + @Nullable Response get(Request request) throws IOException; - CacheRequest put(Response response) throws IOException; + @Nullable CacheRequest put(Response response) throws IOException; /** * Remove any cache entries for the supplied {@code request}. This is invoked when the client diff --git a/okhttp/src/main/java/okhttp3/internal/connection/ConnectionSpecSelector.java b/okhttp/src/main/java/okhttp3/internal/connection/ConnectionSpecSelector.java index 34a116464d80b83817489cf92d0d23814d5bdbeb..285a10ce46af058a0664e323dff542c64b4aebf7 100644 --- a/okhttp/src/main/java/okhttp3/internal/connection/ConnectionSpecSelector.java +++ b/okhttp/src/main/java/okhttp3/internal/connection/ConnectionSpecSelector.java @@ -25,7 +25,6 @@ import java.util.List; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLProtocolException; import javax.net.ssl.SSLSocket; import okhttp3.ConnectionSpec; import okhttp3.internal.Internal; @@ -111,8 +110,7 @@ public final class ConnectionSpecSelector { // Look for known client-side or negotiation errors that are unlikely to be fixed by trying // again with a different connection spec. if (e instanceof SSLHandshakeException) { - // If the problem was a CertificateException from the X509TrustManager, - // do not retry. + // If the problem was a CertificateException from the X509TrustManager, do not retry. if (e.getCause() instanceof CertificateException) { return false; } @@ -122,11 +120,8 @@ public final class ConnectionSpecSelector { return false; } - // On Android, SSLProtocolExceptions can be caused by TLS_FALLBACK_SCSV failures, which means we - // retry those when we probably should not. - return (e instanceof SSLHandshakeException - || e instanceof SSLProtocolException - || e instanceof SSLException); + // Retry for all other SSL failures. + return e instanceof SSLException; } /** diff --git a/okhttp/src/main/java/okhttp3/internal/connection/RealConnection.java b/okhttp/src/main/java/okhttp3/internal/connection/RealConnection.java index 25445fac50663254afb54fb863f52ea0ab4bda38..d087e5da6577524c530d95c5c81ca765d31a57d0 100644 --- a/okhttp/src/main/java/okhttp3/internal/connection/RealConnection.java +++ b/okhttp/src/main/java/okhttp3/internal/connection/RealConnection.java @@ -26,6 +26,7 @@ import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownServiceException; +import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; @@ -323,11 +324,18 @@ public final class RealConnection extends Http2Connection.Listener implements Co // Verify that the socket's certificates are acceptable for the target host. if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) { - X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0); - throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:" - + "\n certificate: " + CertificatePinner.pin(cert) - + "\n DN: " + cert.getSubjectDN().getName() - + "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert)); + List<Certificate> peerCertificates = unverifiedHandshake.peerCertificates(); + if (!peerCertificates.isEmpty()) { + X509Certificate cert = (X509Certificate) peerCertificates.get(0); + throw new SSLPeerUnverifiedException( + "Hostname " + address.url().host() + " not verified:" + + "\n certificate: " + CertificatePinner.pin(cert) + + "\n DN: " + cert.getSubjectDN().getName() + + "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert)); + } else { + throw new SSLPeerUnverifiedException( + "Hostname " + address.url().host() + " not verified (no certificates)"); + } } // Check that the certificate pinner is satisfied by the certificates presented. @@ -392,7 +400,7 @@ public final class RealConnection extends Http2Connection.Listener implements Co // that happens, then we will have buffered bytes that are needed by the SSLSocket! // This check is imperfect: it doesn't tell us whether a handshake will succeed, just // that it will almost certainly fail because the proxy has sent unexpected data. - if (!source.buffer().exhausted() || !sink.buffer().exhausted()) { + if (!source.getBuffer().exhausted() || !sink.buffer().exhausted()) { throw new IOException("TLS tunnel buffered too many bytes!"); } return null; diff --git a/okhttp/src/main/java/okhttp3/internal/connection/StreamAllocation.java b/okhttp/src/main/java/okhttp3/internal/connection/StreamAllocation.java index dcad3d80dbafc8d2c5c388aea87ff908dfcd0164..b36219290707f1a52fe946b178b516552b79d0c1 100644 --- a/okhttp/src/main/java/okhttp3/internal/connection/StreamAllocation.java +++ b/okhttp/src/main/java/okhttp3/internal/connection/StreamAllocation.java @@ -57,14 +57,14 @@ import static okhttp3.internal.Util.closeQuietly; * connections. This class has APIs to release each of the above resources: * * <ul> - * <li>{@link #noNewStreams()} prevents the connection from being used for new streams in the + * <li>{@link #noNewStreams} prevents the connection from being used for new streams in the * future. Use this after a {@code Connection: close} header, or when the connection may be * inconsistent. - * <li>{@link #streamFinished streamFinished()} releases the active stream from this allocation. + * <li>{@link #streamFinished streamFinished} releases the active stream from this allocation. * Note that only one stream may be active at a given time, so it is necessary to call * {@link #streamFinished streamFinished()} before creating a subsequent stream with {@link * #newStream newStream()}. - * <li>{@link #release()} removes the call's hold on the connection. Note that this won't + * <li>{@link #release} removes the call's hold on the connection. Note that this won't * immediately free the connection if there is a stream still lingering. That happens when a * call is complete but its response body has yet to be fully consumed. * </ul> @@ -185,7 +185,7 @@ public final class StreamAllocation { if (result == null) { // Attempt to get a connection from the pool. - Internal.instance.get(connectionPool, address, this, null); + Internal.instance.acquire(connectionPool, address, this, null); if (connection != null) { foundPooledConnection = true; result = connection; @@ -223,7 +223,7 @@ public final class StreamAllocation { List<Route> routes = routeSelection.getAll(); for (int i = 0, size = routes.size(); i < size; i++) { Route route = routes.get(i); - Internal.instance.get(connectionPool, address, this, route); + Internal.instance.acquire(connectionPool, address, this, route); if (connection != null) { foundPooledConnection = true; result = connection; @@ -343,7 +343,7 @@ public final class StreamAllocation { return connection; } - public void release() { + public void release(boolean callEnd) { Socket socket; Connection releasedConnection; synchronized (connectionPool) { @@ -353,9 +353,13 @@ public final class StreamAllocation { } closeQuietly(socket); if (releasedConnection != null) { - Internal.instance.timeoutExit(call, null); + if (callEnd) { + Internal.instance.timeoutExit(call, null); + } eventListener.connectionReleased(call, releasedConnection); - eventListener.callEnd(call); + if (callEnd) { + eventListener.callEnd(call); + } } } diff --git a/okhttp/src/main/java/okhttp3/internal/duplex/DuplexRequestBody.java b/okhttp/src/main/java/okhttp3/internal/duplex/DuplexRequestBody.java new file mode 100644 index 0000000000000000000000000000000000000000..ea00037d61c44b6e54c901f07f2036c5be517610 --- /dev/null +++ b/okhttp/src/main/java/okhttp3/internal/duplex/DuplexRequestBody.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2019 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package okhttp3.internal.duplex; + +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import okio.Sink; + +/** + * A request body that is special in how it is <strong>transmitted</strong> on the network and in + * the <strong>API contract</strong> between OkHttp and the application. + * + * <h3>Duplex Transmission</h3> + * + * <p>With regular HTTP calls the request always completes sending before the response may begin + * receiving. With duplex the request and response may be interleaved! That is, request body bytes + * may be sent after response headers or body bytes have been received. + * + * <p>Though any call may be initiated as a duplex call, only web servers that are specially + * designed for this nonstandard interaction will use it. As of 2019-01, the only widely-used + * implementation of this pattern is gRPC. + * + * <p>Because the encoding of interleaved data is not well-defined for HTTP/1, duplex request bodies + * may only be used with HTTP/2. Calls to HTTP/1 servers will fail before the HTTP request is + * transmitted. + * + * <p>Duplex APIs</p> + * + * <p>With regular request bodies it is not legal to write bytes to the sink passed to {@link + * RequestBody#writeTo} after that method returns. For duplex sinks that condition is lifted. Such + * writes occur on an application-provided thread and may occur concurrently with reads of the + * {@link ResponseBody}. + * + * <p>Signal the end of a duplex request body by calling {@link Sink#close()}. + */ +public interface DuplexRequestBody { + // TODO(jwilson): replace this internal marker interface with a public isDuplex() method? +} diff --git a/okhttp/src/main/java/okhttp3/internal/http/CallServerInterceptor.java b/okhttp/src/main/java/okhttp3/internal/http/CallServerInterceptor.java index 62531aa5140d5996de1e01bb39df139ce5c05e74..ca8a6f517d4f1cf6d0c8221b7eeefe259802c8d0 100644 --- a/okhttp/src/main/java/okhttp3/internal/http/CallServerInterceptor.java +++ b/okhttp/src/main/java/okhttp3/internal/http/CallServerInterceptor.java @@ -17,12 +17,15 @@ package okhttp3.internal.http; import java.io.IOException; import java.net.ProtocolException; +import okhttp3.Call; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; +import okhttp3.internal.Internal; import okhttp3.internal.Util; import okhttp3.internal.connection.RealConnection; import okhttp3.internal.connection.StreamAllocation; +import okhttp3.internal.duplex.DuplexRequestBody; import okio.Buffer; import okio.BufferedSink; import okio.ForwardingSink; @@ -38,17 +41,18 @@ public final class CallServerInterceptor implements Interceptor { } @Override public Response intercept(Chain chain) throws IOException { - RealInterceptorChain realChain = (RealInterceptorChain) chain; - HttpCodec httpCodec = realChain.httpStream(); + final RealInterceptorChain realChain = (RealInterceptorChain) chain; + Call call = realChain.call(); + final HttpCodec httpCodec = realChain.httpStream(); StreamAllocation streamAllocation = realChain.streamAllocation(); RealConnection connection = (RealConnection) realChain.connection(); Request request = realChain.request(); long sentRequestMillis = System.currentTimeMillis(); - realChain.eventListener().requestHeadersStart(realChain.call()); + realChain.eventListener().requestHeadersStart(call); httpCodec.writeRequestHeaders(request); - realChain.eventListener().requestHeadersEnd(realChain.call(), request); + realChain.eventListener().requestHeadersEnd(call, request); Response.Builder responseBuilder = null; if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { @@ -57,22 +61,29 @@ public final class CallServerInterceptor implements Interceptor { // what we did get (such as a 4xx response) without ever transmitting the request body. if ("100-continue".equalsIgnoreCase(request.header("Expect"))) { httpCodec.flushRequest(); - realChain.eventListener().responseHeadersStart(realChain.call()); + realChain.eventListener().responseHeadersStart(call); responseBuilder = httpCodec.readResponseHeaders(true); } if (responseBuilder == null) { - // Write the request body if the "Expect: 100-continue" expectation was met. - realChain.eventListener().requestBodyStart(realChain.call()); - long contentLength = request.body().contentLength(); - CountingSink requestBodyOut = - new CountingSink(httpCodec.createRequestBody(request, contentLength)); - BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); - - request.body().writeTo(bufferedRequestBody); - bufferedRequestBody.close(); - realChain.eventListener() - .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount); + if (request.body() instanceof DuplexRequestBody) { + // Prepare a duplex body so that the application can send a request body later. + httpCodec.flushRequest(); + CountingSink requestBodyOut = new CountingSink(httpCodec.createRequestBody(request, -1L)); + BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); + request.body().writeTo(bufferedRequestBody); + } else { + // Write the request body if the "Expect: 100-continue" expectation was met. + realChain.eventListener().requestBodyStart(call); + long contentLength = request.body().contentLength(); + CountingSink requestBodyOut = + new CountingSink(httpCodec.createRequestBody(request, contentLength)); + BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); + + request.body().writeTo(bufferedRequestBody); + bufferedRequestBody.close(); + realChain.eventListener().requestBodyEnd(call, requestBodyOut.successfulCount); + } } else if (!connection.isMultiplexed()) { // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection // from being reused. Otherwise we're still obligated to transmit the request body to @@ -81,19 +92,22 @@ public final class CallServerInterceptor implements Interceptor { } } - httpCodec.finishRequest(); + if (!(request.body() instanceof DuplexRequestBody)) { + httpCodec.finishRequest(); + } if (responseBuilder == null) { - realChain.eventListener().responseHeadersStart(realChain.call()); + realChain.eventListener().responseHeadersStart(call); responseBuilder = httpCodec.readResponseHeaders(false); } - Response response = responseBuilder + responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) - .receivedResponseAtMillis(System.currentTimeMillis()) - .build(); + .receivedResponseAtMillis(System.currentTimeMillis()); + Internal.instance.initCodec(responseBuilder, httpCodec); + Response response = responseBuilder.build(); int code = response.code(); if (code == 100) { @@ -101,18 +115,18 @@ public final class CallServerInterceptor implements Interceptor { // try again to read the actual response responseBuilder = httpCodec.readResponseHeaders(false); - response = responseBuilder - .request(request) - .handshake(streamAllocation.connection().handshake()) - .sentRequestAtMillis(sentRequestMillis) - .receivedResponseAtMillis(System.currentTimeMillis()) - .build(); + responseBuilder + .request(request) + .handshake(streamAllocation.connection().handshake()) + .sentRequestAtMillis(sentRequestMillis) + .receivedResponseAtMillis(System.currentTimeMillis()); + Internal.instance.initCodec(responseBuilder, httpCodec); + response = responseBuilder.build(); code = response.code(); } - realChain.eventListener() - .responseHeadersEnd(realChain.call(), response); + realChain.eventListener().responseHeadersEnd(call, response); if (forWebSocket && code == 101) { // Connection is upgrading, but we need to ensure interceptors see a non-null response body. diff --git a/okhttp/src/main/java/okhttp3/internal/http/HttpCodec.java b/okhttp/src/main/java/okhttp3/internal/http/HttpCodec.java index ad9759acce3ed220a38a513d07acec850dc4958a..103fce6662e9f943efb0d3aa6276d24636fadeea 100644 --- a/okhttp/src/main/java/okhttp3/internal/http/HttpCodec.java +++ b/okhttp/src/main/java/okhttp3/internal/http/HttpCodec.java @@ -16,6 +16,7 @@ package okhttp3.internal.http; import java.io.IOException; +import okhttp3.Headers; import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; @@ -53,6 +54,9 @@ public interface HttpCodec { /** Returns a stream that reads the response body. */ ResponseBody openResponseBody(Response response) throws IOException; + /** Returns the trailers after the HTTP response. May be empty. */ + Headers trailers() throws IOException; + /** * Cancel this stream. Resources held by this stream will be cleaned up, though not synchronously. * That may happen later by the connection pool thread. diff --git a/okhttp/src/main/java/okhttp3/internal/http/HttpHeaders.java b/okhttp/src/main/java/okhttp3/internal/http/HttpHeaders.java index 479fe07a24fff2acedee0a5eef5abd55dcc33717..7712934efbbf88cc6908a67a655dbc3ac05368cb 100644 --- a/okhttp/src/main/java/okhttp3/internal/http/HttpHeaders.java +++ b/okhttp/src/main/java/okhttp3/internal/http/HttpHeaders.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.TreeSet; import okhttp3.Challenge; @@ -36,7 +37,7 @@ import okio.ByteString; import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED; import static java.net.HttpURLConnection.HTTP_NO_CONTENT; -import static okhttp3.internal.Util.equal; +import static okhttp3.internal.Util.EMPTY_HEADERS; import static okhttp3.internal.http.StatusLine.HTTP_CONTINUE; /** Headers and utilities for internal use by OkHttp. */ @@ -71,7 +72,7 @@ public final class HttpHeaders { public static boolean varyMatches( Response cachedResponse, Headers cachedRequest, Request newRequest) { for (String field : varyFields(cachedResponse)) { - if (!equal(cachedRequest.values(field), newRequest.headers(field))) return false; + if (!Objects.equals(cachedRequest.values(field), newRequest.headers(field))) return false; } return true; } @@ -132,7 +133,7 @@ public final class HttpHeaders { */ public static Headers varyHeaders(Headers requestHeaders, Headers responseHeaders) { Set<String> varyFields = varyFields(responseHeaders); - if (varyFields.isEmpty()) return new Headers.Builder().build(); + if (varyFields.isEmpty()) return EMPTY_HEADERS; Headers.Builder result = new Headers.Builder(); for (int i = 0, size = requestHeaders.size(); i < size; i++) { @@ -194,7 +195,7 @@ public final class HttpHeaders { peek = readToken(header); if (peek == null) { if (!header.exhausted()) return; // Expected a token; got something else. - result.add(new Challenge(schemeName, Collections.<String, String>emptyMap())); + result.add(new Challenge(schemeName, Collections.emptyMap())); return; } @@ -204,7 +205,7 @@ public final class HttpHeaders { // It's a token68 because there isn't a value after it. if (!commaPrefixed && (commaSuffixed || header.exhausted())) { result.add(new Challenge(schemeName, Collections.singletonMap( - (String) null, peek + repeat('=', eqCount)))); + null, peek + repeat('=', eqCount)))); peek = null; continue; } diff --git a/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java b/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java index d682de8bf04f637d7667d8c6e64bcd8aee5f315a..fa72df373b22af48773bb5a5e8325f66f142878f 100644 --- a/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java +++ b/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java @@ -15,6 +15,7 @@ */ package okhttp3.internal.http; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InterruptedIOException; import java.net.HttpRetryException; @@ -65,14 +66,12 @@ public final class RetryAndFollowUpInterceptor implements Interceptor { private static final int MAX_FOLLOW_UPS = 20; private final OkHttpClient client; - private final boolean forWebSocket; private volatile StreamAllocation streamAllocation; private Object callStackTrace; private volatile boolean canceled; - public RetryAndFollowUpInterceptor(OkHttpClient client, boolean forWebSocket) { + public RetryAndFollowUpInterceptor(OkHttpClient client) { this.client = client; - this.forWebSocket = forWebSocket; } /** @@ -116,7 +115,7 @@ public final class RetryAndFollowUpInterceptor implements Interceptor { Response priorResponse = null; while (true) { if (canceled) { - streamAllocation.release(); + streamAllocation.release(true); throw new IOException("Canceled"); } @@ -142,7 +141,7 @@ public final class RetryAndFollowUpInterceptor implements Interceptor { // We're throwing an unchecked exception. Release any resources. if (releaseConnection) { streamAllocation.streamFailed(null); - streamAllocation.release(); + streamAllocation.release(true); } } @@ -159,29 +158,29 @@ public final class RetryAndFollowUpInterceptor implements Interceptor { try { followUp = followUpRequest(response, streamAllocation.route()); } catch (IOException e) { - streamAllocation.release(); + streamAllocation.release(true); throw e; } if (followUp == null) { - streamAllocation.release(); + streamAllocation.release(true); return response; } closeQuietly(response.body()); if (++followUpCount > MAX_FOLLOW_UPS) { - streamAllocation.release(); + streamAllocation.release(true); throw new ProtocolException("Too many follow-up requests: " + followUpCount); } if (followUp.body() instanceof UnrepeatableRequestBody) { - streamAllocation.release(); + streamAllocation.release(true); throw new HttpRetryException("Cannot retry streamed HTTP body", response.code()); } if (!sameConnection(response, followUp.url())) { - streamAllocation.release(); + streamAllocation.release(false); streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(followUp.url()), call, eventListener, callStackTrace); this.streamAllocation = streamAllocation; @@ -224,7 +223,7 @@ public final class RetryAndFollowUpInterceptor implements Interceptor { if (!client.retryOnConnectionFailure()) return false; // We can't send the request body again. - if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false; + if (requestSendStarted && requestIsUnrepeatable(e, userRequest)) return false; // This exception is fatal. if (!isRecoverable(e, requestSendStarted)) return false; @@ -236,6 +235,11 @@ public final class RetryAndFollowUpInterceptor implements Interceptor { return true; } + private boolean requestIsUnrepeatable(IOException e, Request userRequest) { + return userRequest.body() instanceof UnrepeatableRequestBody + || e instanceof FileNotFoundException; + } + private boolean isRecoverable(IOException e, boolean requestSendStarted) { // If there was a protocol problem, don't recover. if (e instanceof ProtocolException) { diff --git a/okhttp/src/main/java/okhttp3/internal/http1/Http1Codec.java b/okhttp/src/main/java/okhttp3/internal/http1/Http1Codec.java index 6c7b4373c441d21945f5057f5df32ebff44ae93b..6dc70c48651b054b069e77c3ae019bfc4b9b2b09 100644 --- a/okhttp/src/main/java/okhttp3/internal/http1/Http1Codec.java +++ b/okhttp/src/main/java/okhttp3/internal/http1/Http1Codec.java @@ -86,6 +86,12 @@ public final class Http1Codec implements HttpCodec { int state = STATE_IDLE; private long headerLimit = HEADER_LIMIT; + /** + * Received trailers. Null unless the response body uses chunked transfer-encoding and includes + * trailers. Undefined until the end of the response body. + */ + private Headers trailers; + public Http1Codec(OkHttpClient client, StreamAllocation streamAllocation, BufferedSource source, BufferedSink sink) { this.client = client; @@ -153,6 +159,13 @@ public final class Http1Codec implements HttpCodec { return new RealResponseBody(contentType, -1L, Okio.buffer(newUnknownLengthSource())); } + @Override public Headers trailers() throws IOException { + if (state != STATE_CLOSED) { + throw new IllegalStateException("too early; can't read the trailers yet"); + } + return trailers != null ? trailers : Util.EMPTY_HEADERS; + } + /** Returns true if this connection is closed. */ public boolean isClosed() { return state == STATE_CLOSED; @@ -205,9 +218,7 @@ public final class Http1Codec implements HttpCodec { return responseBuilder; } catch (EOFException e) { // Provide more context if the server ends the stream before sending a response. - IOException exception = new IOException("unexpected end of stream on " + streamAllocation); - exception.initCause(e); - throw exception; + throw new IOException("unexpected end of stream on " + streamAllocation, e); } } @@ -477,7 +488,8 @@ public final class Http1Codec implements HttpCodec { } if (bytesRemainingInChunk == 0L) { hasMoreChunks = false; - HttpHeaders.receiveHeaders(client.cookieJar(), url, readHeaders()); + trailers = readHeaders(); + HttpHeaders.receiveHeaders(client.cookieJar(), url, trailers); endOfInput(true, null); } } diff --git a/okhttp/src/main/java/okhttp3/internal/http2/Header.java b/okhttp/src/main/java/okhttp3/internal/http2/Header.java index 362570a2bcd285b8a60be6bf8a79e2a0b19f9ada..a306ac4622ec65da23015d1d1e9307e6cc319c05 100644 --- a/okhttp/src/main/java/okhttp3/internal/http2/Header.java +++ b/okhttp/src/main/java/okhttp3/internal/http2/Header.java @@ -15,7 +15,6 @@ */ package okhttp3.internal.http2; -import okhttp3.Headers; import okhttp3.internal.Util; import okio.ByteString; @@ -77,9 +76,4 @@ public final class Header { @Override public String toString() { return Util.format("%s: %s", name.utf8(), value.utf8()); } - - // TODO(jwilson): move this to Headers? - interface Listener { - void onHeaders(Headers headers); - } } diff --git a/okhttp/src/main/java/okhttp3/internal/http2/Hpack.java b/okhttp/src/main/java/okhttp3/internal/http2/Hpack.java index 258ac9b2693c609d2758cfd7eaef2216d14ebabd..615756cc4864e7fe154f0bde0b0e25713d667033 100644 --- a/okhttp/src/main/java/okhttp3/internal/http2/Hpack.java +++ b/okhttp/src/main/java/okhttp3/internal/http2/Hpack.java @@ -22,7 +22,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import okhttp3.internal.Util; +import java.util.Objects; import okio.Buffer; import okio.BufferedSource; import okio.ByteString; @@ -483,9 +483,9 @@ final class Hpack { // it's unnecessary to waste cycles looking at them. This check is built on the // observation that the header entries we care about are in adjacent pairs, and we // always know the first index of the pair. - if (Util.equal(STATIC_HEADER_TABLE[headerNameIndex - 1].value, value)) { + if (Objects.equals(STATIC_HEADER_TABLE[headerNameIndex - 1].value, value)) { headerIndex = headerNameIndex; - } else if (Util.equal(STATIC_HEADER_TABLE[headerNameIndex].value, value)) { + } else if (Objects.equals(STATIC_HEADER_TABLE[headerNameIndex].value, value)) { headerIndex = headerNameIndex + 1; } } @@ -493,8 +493,8 @@ final class Hpack { if (headerIndex == -1) { for (int j = nextHeaderIndex + 1, length = dynamicTable.length; j < length; j++) { - if (Util.equal(dynamicTable[j].name, name)) { - if (Util.equal(dynamicTable[j].value, value)) { + if (Objects.equals(dynamicTable[j].name, name)) { + if (Objects.equals(dynamicTable[j].value, value)) { headerIndex = j - nextHeaderIndex + STATIC_HEADER_TABLE.length; break; } else if (headerNameIndex == -1) { diff --git a/okhttp/src/main/java/okhttp3/internal/http2/Http2Codec.java b/okhttp/src/main/java/okhttp3/internal/http2/Http2Codec.java index 9a81d536f2bc8ec34498fa669e26b84889d36e5e..9200e6ef1a089248dc540bd61ef2f87508b053bc 100644 --- a/okhttp/src/main/java/okhttp3/internal/http2/Http2Codec.java +++ b/okhttp/src/main/java/okhttp3/internal/http2/Http2Codec.java @@ -37,7 +37,6 @@ import okhttp3.internal.http.RealResponseBody; import okhttp3.internal.http.RequestLine; import okhttp3.internal.http.StatusLine; import okio.Buffer; -import okio.ByteString; import okio.ForwardingSource; import okio.Okio; import okio.Sink; @@ -92,8 +91,9 @@ public final class Http2Codec implements HttpCodec { private final Interceptor.Chain chain; final StreamAllocation streamAllocation; private final Http2Connection connection; - private Http2Stream stream; + private volatile Http2Stream stream; private final Protocol protocol; + private volatile boolean canceled; public Http2Codec(OkHttpClient client, Interceptor.Chain chain, StreamAllocation streamAllocation, Http2Connection connection) { @@ -115,6 +115,12 @@ public final class Http2Codec implements HttpCodec { boolean hasRequestBody = request.body() != null; List<Header> requestHeaders = http2HeadersList(request); stream = connection.newStream(requestHeaders, hasRequestBody); + // We may have been asked to cancel while creating the new stream and sending the request + // headers, but there was still no stream to close. + if (canceled) { + stream.closeLater(ErrorCode.CANCEL); + throw new IOException("Canceled"); + } stream.readTimeout().timeout(chain.readTimeoutMillis(), TimeUnit.MILLISECONDS); stream.writeTimeout().timeout(chain.writeTimeoutMillis(), TimeUnit.MILLISECONDS); } @@ -149,8 +155,9 @@ public final class Http2Codec implements HttpCodec { for (int i = 0, size = headers.size(); i < size; i++) { // header names must be lowercase. - ByteString name = ByteString.encodeUtf8(headers.name(i).toLowerCase(Locale.US)); - if (!HTTP_2_SKIPPED_REQUEST_HEADERS.contains(name.utf8())) { + String name = headers.name(i).toLowerCase(Locale.US); + if (!HTTP_2_SKIPPED_REQUEST_HEADERS.contains(name) + || name.equals(TE) && headers.value(i).equals("trailers")) { result.add(new Header(name, headers.value(i))); } } @@ -188,7 +195,12 @@ public final class Http2Codec implements HttpCodec { return new RealResponseBody(contentType, contentLength, Okio.buffer(source)); } + @Override public Headers trailers() throws IOException { + return stream.trailers(); + } + @Override public void cancel() { + canceled = true; if (stream != null) stream.closeLater(ErrorCode.CANCEL); } diff --git a/okhttp/src/main/java/okhttp3/internal/http2/Http2Connection.java b/okhttp/src/main/java/okhttp3/internal/http2/Http2Connection.java index aa77584e233051d3e00abd1d4c5e626b1f21a19a..cf1413f886b1739bc8dd0cc76b1b9327dd7509af 100644 --- a/okhttp/src/main/java/okhttp3/internal/http2/Http2Connection.java +++ b/okhttp/src/main/java/okhttp3/internal/http2/Http2Connection.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InterruptedIOException; import java.net.InetSocketAddress; import java.net.Socket; +import java.net.SocketAddress; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -34,7 +35,6 @@ import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import okhttp3.Headers; -import okhttp3.Protocol; import okhttp3.internal.NamedRunnable; import okhttp3.internal.Util; import okhttp3.internal.platform.Platform; @@ -79,7 +79,7 @@ public final class Http2Connection implements Closeable { * threads because listeners are not required to return promptly. */ private static final ExecutorService listenerExecutor = new ThreadPoolExecutor(0, - Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), + Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<>(), Util.threadFactory("OkHttp Http2Connection", true)); /** True if this peer initiated the connection. */ @@ -91,7 +91,7 @@ public final class Http2Connection implements Closeable { */ final Listener listener; final Map<Integer, Http2Stream> streams = new LinkedHashMap<>(); - final String hostname; + final String connectionName; int lastGoodStreamId; int nextStreamId; boolean shutdown; @@ -153,19 +153,18 @@ public final class Http2Connection implements Closeable { okHttpSettings.set(Settings.INITIAL_WINDOW_SIZE, OKHTTP_CLIENT_WINDOW_SIZE); } - hostname = builder.hostname; + connectionName = builder.connectionName; writerExecutor = new ScheduledThreadPoolExecutor(1, - Util.threadFactory(Util.format("OkHttp %s Writer", hostname), false)); + Util.threadFactory(Util.format("OkHttp %s Writer", connectionName), false)); if (builder.pingIntervalMillis != 0) { writerExecutor.scheduleAtFixedRate(new PingRunnable(false, 0, 0), builder.pingIntervalMillis, builder.pingIntervalMillis, MILLISECONDS); } // Like newSingleThreadExecutor, except lazy creates the thread. - pushExecutor = new ThreadPoolExecutor(0, 1, 60, TimeUnit.SECONDS, - new LinkedBlockingQueue<Runnable>(), - Util.threadFactory(Util.format("OkHttp %s Push Observer", hostname), true)); + pushExecutor = new ThreadPoolExecutor(0, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), + Util.threadFactory(Util.format("OkHttp %s Push Observer", connectionName), true)); peerSettings.set(Settings.INITIAL_WINDOW_SIZE, DEFAULT_INITIAL_WINDOW_SIZE); peerSettings.set(Settings.MAX_FRAME_SIZE, Http2.INITIAL_MAX_FRAME_SIZE); bytesLeftInWriteWindow = peerSettings.getInitialWindowSize(); @@ -175,11 +174,6 @@ public final class Http2Connection implements Closeable { readerRunnable = new ReaderRunnable(new Http2Reader(builder.source, client)); } - /** The protocol as selected using ALPN. */ - public Protocol getProtocol() { - return Protocol.HTTP_2; - } - /** * Returns the number of {@link Http2Stream#isOpen() open streams} on this connection. */ @@ -256,7 +250,7 @@ public final class Http2Connection implements Closeable { } } if (associatedStreamId == 0) { - writer.synStream(outFinished, streamId, associatedStreamId, requestHeaders); + writer.headers(outFinished, streamId, requestHeaders); } else if (client) { throw new IllegalArgumentException("client streams shouldn't have associated stream IDs"); } else { // HTTP/2 has a PUSH_PROMISE frame. @@ -271,9 +265,9 @@ public final class Http2Connection implements Closeable { return stream; } - void writeSynReply(int streamId, boolean outFinished, List<Header> alternating) + void writeHeaders(int streamId, boolean outFinished, List<Header> alternating) throws IOException { - writer.synReply(outFinished, streamId, alternating); + writer.headers(outFinished, streamId, alternating); } /** @@ -324,7 +318,7 @@ public final class Http2Connection implements Closeable { void writeSynResetLater(final int streamId, final ErrorCode errorCode) { try { - writerExecutor.execute(new NamedRunnable("OkHttp %s stream %d", hostname, streamId) { + writerExecutor.execute(new NamedRunnable("OkHttp %s stream %d", connectionName, streamId) { @Override public void execute() { try { writeSynReset(streamId, errorCode); @@ -345,7 +339,7 @@ public final class Http2Connection implements Closeable { void writeWindowUpdateLater(final int streamId, final long unacknowledgedBytesRead) { try { writerExecutor.execute( - new NamedRunnable("OkHttp Window Update %s stream %d", hostname, streamId) { + new NamedRunnable("OkHttp Window Update %s stream %d", connectionName, streamId) { @Override public void execute() { try { writer.windowUpdate(streamId, unacknowledgedBytesRead); @@ -365,7 +359,7 @@ public final class Http2Connection implements Closeable { final int payload2; PingRunnable(boolean reply, int payload1, int payload2) { - super("OkHttp %s ping %08x%08x", hostname, payload1, payload2); + super("OkHttp %s ping %08x%08x", connectionName, payload1, payload2); this.reply = reply; this.payload1 = payload1; this.payload2 = payload2; @@ -540,7 +534,7 @@ public final class Http2Connection implements Closeable { public static class Builder { Socket socket; - String hostname; + String connectionName; BufferedSource source; BufferedSink sink; Listener listener = Listener.REFUSE_INCOMING_STREAMS; @@ -557,14 +551,18 @@ public final class Http2Connection implements Closeable { } public Builder socket(Socket socket) throws IOException { - return socket(socket, ((InetSocketAddress) socket.getRemoteSocketAddress()).getHostName(), + SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress(); + String connectionName = remoteSocketAddress instanceof InetSocketAddress + ? ((InetSocketAddress) remoteSocketAddress).getHostName() + : remoteSocketAddress.toString(); + return socket(socket, connectionName, Okio.buffer(Okio.source(socket)), Okio.buffer(Okio.sink(socket))); } public Builder socket( - Socket socket, String hostname, BufferedSource source, BufferedSink sink) { + Socket socket, String connectionName, BufferedSource source, BufferedSink sink) { this.socket = socket; - this.hostname = hostname; + this.connectionName = connectionName; this.source = source; this.sink = sink; return this; @@ -598,7 +596,7 @@ public final class Http2Connection implements Closeable { final Http2Reader reader; ReaderRunnable(Http2Reader reader) { - super("OkHttp %s", hostname); + super("OkHttp %s", connectionName); this.reader = reader; } @@ -638,7 +636,7 @@ public final class Http2Connection implements Closeable { } dataStream.receiveData(source, length); if (inFinished) { - dataStream.receiveFin(); + dataStream.receiveHeaders(Util.EMPTY_HEADERS, true); } } @@ -668,12 +666,14 @@ public final class Http2Connection implements Closeable { false, inFinished, headers); lastGoodStreamId = streamId; streams.put(streamId, newStream); - listenerExecutor.execute(new NamedRunnable("OkHttp %s stream %d", hostname, streamId) { + listenerExecutor.execute(new NamedRunnable( + "OkHttp %s stream %d", connectionName, streamId) { @Override public void execute() { try { listener.onStream(newStream); } catch (IOException e) { - Platform.get().log(INFO, "Http2Connection.Listener failure for " + hostname, e); + Platform.get().log( + INFO, "Http2Connection.Listener failure for " + connectionName, e); try { newStream.close(ErrorCode.PROTOCOL_ERROR); } catch (IOException ignored) { @@ -686,8 +686,7 @@ public final class Http2Connection implements Closeable { } // Update an existing stream. - stream.receiveHeaders(headerBlock); - if (inFinished) stream.receiveFin(); + stream.receiveHeaders(Util.toHeaders(headerBlock), inFinished); } @Override public void rstStream(int streamId, ErrorCode errorCode) { @@ -719,7 +718,7 @@ public final class Http2Connection implements Closeable { streamsToNotify = streams.values().toArray(new Http2Stream[streams.size()]); } } - listenerExecutor.execute(new NamedRunnable("OkHttp %s settings", hostname) { + listenerExecutor.execute(new NamedRunnable("OkHttp %s settings", connectionName) { @Override public void execute() { listener.onSettings(Http2Connection.this); } @@ -736,7 +735,7 @@ public final class Http2Connection implements Closeable { private void applyAndAckSettings(final Settings peerSettings) { try { - writerExecutor.execute(new NamedRunnable("OkHttp %s ACK Settings", hostname) { + writerExecutor.execute(new NamedRunnable("OkHttp %s ACK Settings", connectionName) { @Override public void execute() { try { writer.applyAndAckSettings(peerSettings); @@ -839,7 +838,8 @@ public final class Http2Connection implements Closeable { currentPushRequests.add(streamId); } try { - pushExecutorExecute(new NamedRunnable("OkHttp %s Push Request[%s]", hostname, streamId) { + pushExecutorExecute(new NamedRunnable( + "OkHttp %s Push Request[%s]", connectionName, streamId) { @Override public void execute() { boolean cancel = pushObserver.onRequest(streamId, requestHeaders); try { @@ -861,7 +861,8 @@ public final class Http2Connection implements Closeable { void pushHeadersLater(final int streamId, final List<Header> requestHeaders, final boolean inFinished) { try { - pushExecutorExecute(new NamedRunnable("OkHttp %s Push Headers[%s]", hostname, streamId) { + pushExecutorExecute(new NamedRunnable( + "OkHttp %s Push Headers[%s]", connectionName, streamId) { @Override public void execute() { boolean cancel = pushObserver.onHeaders(streamId, requestHeaders, inFinished); try { @@ -890,7 +891,7 @@ public final class Http2Connection implements Closeable { source.require(byteCount); // Eagerly read the frame before firing client thread. source.read(buffer, byteCount); if (buffer.size() != byteCount) throw new IOException(buffer.size() + " != " + byteCount); - pushExecutorExecute(new NamedRunnable("OkHttp %s Push Data[%s]", hostname, streamId) { + pushExecutorExecute(new NamedRunnable("OkHttp %s Push Data[%s]", connectionName, streamId) { @Override public void execute() { try { boolean cancel = pushObserver.onData(streamId, buffer, byteCount, inFinished); @@ -907,7 +908,7 @@ public final class Http2Connection implements Closeable { } void pushResetLater(final int streamId, final ErrorCode errorCode) { - pushExecutorExecute(new NamedRunnable("OkHttp %s Push Reset[%s]", hostname, streamId) { + pushExecutorExecute(new NamedRunnable("OkHttp %s Push Reset[%s]", connectionName, streamId) { @Override public void execute() { pushObserver.onReset(streamId, errorCode); synchronized (Http2Connection.this) { diff --git a/okhttp/src/main/java/okhttp3/internal/http2/Http2Stream.java b/okhttp/src/main/java/okhttp3/internal/http2/Http2Stream.java index c391b389523c986c8ddd9c0ab13f2755777a9aab..3431901fa5af201bc3a8663f10319995ddf01f45 100644 --- a/okhttp/src/main/java/okhttp3/internal/http2/Http2Stream.java +++ b/okhttp/src/main/java/okhttp3/internal/http2/Http2Stream.java @@ -20,7 +20,6 @@ import java.io.IOException; import java.io.InterruptedIOException; import java.net.SocketTimeoutException; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Deque; import java.util.List; import javax.annotation.Nullable; @@ -61,7 +60,6 @@ public final class Http2Stream { * read}. */ private final Deque<Headers> headersQueue = new ArrayDeque<>(); - private Header.Listener headersListener; /** True if response headers have been sent or received. */ private boolean hasResponseHeaders; @@ -158,6 +156,20 @@ public final class Http2Stream { throw new StreamResetException(errorCode); } + /** + * Returns the trailers. It is only safe to call this once the source stream has been completely + * exhausted. + */ + public synchronized Headers trailers() throws IOException { + if (errorCode != null) { + throw new StreamResetException(errorCode); + } + if (!source.finished || !source.receiveBuffer.exhausted() || !source.readBuffer.exhausted()) { + throw new IllegalStateException("too early; can't read the trailers yet"); + } + return source.trailers != null ? source.trailers : Util.EMPTY_HEADERS; + } + /** * Returns the reason why this stream was closed, or null if it closed normally or has not yet * been closed. @@ -169,22 +181,21 @@ public final class Http2Stream { /** * Sends a reply to an incoming stream. * - * @param out true to create an output stream that we can use to send data to the remote peer. - * Corresponds to {@code FLAG_FIN}. + * @param outFinished true to eagerly finish the output stream to send data to the remote peer. + * Corresponds to {@code FLAG_FIN}. + * @param flushHeaders true to force flush the response headers. This should be true unless the + * response body exists and will be written immediately. */ - public void writeHeaders(List<Header> responseHeaders, boolean out) throws IOException { + public void writeHeaders(List<Header> responseHeaders, boolean outFinished, boolean flushHeaders) + throws IOException { assert (!Thread.holdsLock(Http2Stream.this)); if (responseHeaders == null) { throw new NullPointerException("headers == null"); } - boolean outFinished = false; - boolean flushHeaders = false; synchronized (this) { this.hasResponseHeaders = true; - if (!out) { + if (outFinished) { this.sink.finished = true; - flushHeaders = true; - outFinished = true; } } @@ -196,14 +207,21 @@ public final class Http2Stream { } } - // TODO(jwilson): rename to writeHeaders - connection.writeSynReply(id, outFinished, responseHeaders); + connection.writeHeaders(id, outFinished, responseHeaders); if (flushHeaders) { connection.flush(); } } + public void enqueueTrailers(Headers trailers) { + synchronized (this) { + if (sink.finished) throw new IllegalStateException("already finished"); + if (trailers.size() == 0) throw new IllegalArgumentException("trailers.size() == 0"); + this.sink.trailers = trailers; + } + } + public Timeout readTimeout() { return readTimeout; } @@ -221,7 +239,7 @@ public final class Http2Stream { * Returns a sink that can be used to write data to the peer. * * @throws IllegalStateException if this stream was initiated by the peer and a {@link - * #writeHeaders} has not yet been sent. + * #writeHeaders} has not yet been sent. */ public Sink getSink() { synchronized (this) { @@ -271,34 +289,28 @@ public final class Http2Stream { return true; } - /** - * Accept headers from the network and store them until the client calls {@link #takeHeaders}, or - * {@link FramingSource#read} them. - */ - void receiveHeaders(List<Header> headers) { - assert (!Thread.holdsLock(Http2Stream.this)); - boolean open; - synchronized (this) { - hasResponseHeaders = true; - headersQueue.add(Util.toHeaders(headers)); - open = isOpen(); - notifyAll(); - } - if (!open) { - connection.removeStream(id); - } - } - void receiveData(BufferedSource in, int length) throws IOException { assert (!Thread.holdsLock(Http2Stream.this)); this.source.receive(in, length); } - void receiveFin() { + /** + * Accept headers from the network and store them until the client calls {@link #takeHeaders}, or + * {@link FramingSource#read} them. + */ + void receiveHeaders(Headers headers, boolean inFinished) { assert (!Thread.holdsLock(Http2Stream.this)); boolean open; synchronized (this) { - this.source.finished = true; + if (!hasResponseHeaders || !inFinished) { + hasResponseHeaders = true; + headersQueue.add(headers); + } else { + this.source.trailers = headers; + } + if (inFinished) { + this.source.finished = true; + } open = isOpen(); notifyAll(); } @@ -314,13 +326,6 @@ public final class Http2Stream { } } - public synchronized void setHeadersListener(Header.Listener headersListener) { - this.headersListener = headersListener; - if (!headersQueue.isEmpty() && headersListener != null) { - notifyAll(); // We now have somewhere to deliver headers! - } - } - /** * A source that reads the incoming data frames of a stream. Although this class uses * synchronization to safely receive incoming data frames, it is not intended for use by multiple @@ -336,6 +341,12 @@ public final class Http2Stream { /** Maximum number of bytes to buffer before reporting a flow control error. */ private final long maxByteCount; + /** + * Received trailers. Null unless the server has provided trailers. Undefined until the stream + * is exhausted. Guarded by Http2Stream.this. + */ + private Headers trailers; + /** True if the caller has closed this stream. */ boolean closed; @@ -353,8 +364,6 @@ public final class Http2Stream { if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); while (true) { - Headers headersToDeliver = null; - Header.Listener headersListenerToNotify = null; long readBytesDelivered = -1; ErrorCode errorCodeToDeliver = null; @@ -371,11 +380,6 @@ public final class Http2Stream { if (closed) { throw new IOException("stream closed"); - } else if (!headersQueue.isEmpty() && headersListener != null) { - // Prepare to deliver headers. - headersToDeliver = headersQueue.removeFirst(); - headersListenerToNotify = headersListener; - } else if (readBuffer.size() > 0) { // Prepare to read bytes. Start by moving them to the caller's buffer. readBytesDelivered = readBuffer.read(sink, Math.min(byteCount, readBuffer.size())); @@ -401,11 +405,6 @@ public final class Http2Stream { // 2. Do it outside of the synchronized block and timeout. - if (headersToDeliver != null && headersListenerToNotify != null) { - headersListenerToNotify.onHeaders(headersToDeliver); - continue; - } - if (readBytesDelivered != -1) { // Update connection.unacknowledgedBytesRead outside the synchronized block. updateConnectionFlowControl(readBytesDelivered); @@ -475,28 +474,16 @@ public final class Http2Stream { @Override public void close() throws IOException { long bytesDiscarded; - List<Headers> headersToDeliver = null; - Header.Listener headersListenerToNotify = null; synchronized (Http2Stream.this) { closed = true; bytesDiscarded = readBuffer.size(); readBuffer.clear(); - if (!headersQueue.isEmpty() && headersListener != null) { - headersToDeliver = new ArrayList<>(headersQueue); - headersQueue.clear(); - headersListenerToNotify = headersListener; - } Http2Stream.this.notifyAll(); // TODO(jwilson): Unnecessary? } if (bytesDiscarded > 0) { updateConnectionFlowControl(bytesDiscarded); } cancelStreamIfNecessary(); - if (headersListenerToNotify != null) { - for (Headers headers : headersToDeliver) { - headersListenerToNotify.onHeaders(headers); - } - } } } @@ -529,6 +516,9 @@ public final class Http2Stream { */ private final Buffer sendBuffer = new Buffer(); + /** Trailers to send at the end of the stream. */ + private Headers trailers; + boolean closed; /** @@ -548,7 +538,7 @@ public final class Http2Stream { * Emit a single data frame to the connection. The frame's size be limited by this stream's * write window. This method will block until the write window is nonempty. */ - private void emitFrame(boolean outFinished) throws IOException { + private void emitFrame(boolean outFinishedOnLastFrame) throws IOException { long toWrite; synchronized (Http2Stream.this) { writeTimeout.enter(); @@ -567,7 +557,8 @@ public final class Http2Stream { writeTimeout.enter(); try { - connection.writeData(id, outFinished && toWrite == sendBuffer.size(), sendBuffer, toWrite); + boolean outFinished = outFinishedOnLastFrame && toWrite == sendBuffer.size(); + connection.writeData(id, outFinished, sendBuffer, toWrite); } finally { writeTimeout.exitAndThrowIfTimedOut(); } @@ -594,13 +585,21 @@ public final class Http2Stream { if (closed) return; } if (!sink.finished) { - // Emit the remaining data, setting the END_STREAM flag on the last frame. - if (sendBuffer.size() > 0) { + // We have 0 or more frames of data, and 0 or more frames of trailers. We need to send at + // least one frame with the END_STREAM flag set. That must be the last frame, and the + // trailers must be sent after all of the data. + boolean hasData = sendBuffer.size() > 0; + boolean hasTrailers = trailers != null; + if (hasTrailers) { + while (sendBuffer.size() > 0) { + emitFrame(false); + } + connection.writeHeaders(id, true, Util.toHeaderBlock(trailers)); + } else if (hasData) { while (sendBuffer.size() > 0) { emitFrame(true); } } else { - // Send an empty frame just so we can set the END_STREAM flag. connection.writeData(id, true, null, 0); } } diff --git a/okhttp/src/main/java/okhttp3/internal/http2/Http2Writer.java b/okhttp/src/main/java/okhttp3/internal/http2/Http2Writer.java index 5ef6bb9c4a67a1881a416e7f78936344a618691d..b6456949de0a0ed052b2f5b64e3cd34a477a1f26 100644 --- a/okhttp/src/main/java/okhttp3/internal/http2/Http2Writer.java +++ b/okhttp/src/main/java/okhttp3/internal/http2/Http2Writer.java @@ -121,24 +121,6 @@ final class Http2Writer implements Closeable { sink.flush(); } - public synchronized void synStream(boolean outFinished, int streamId, - int associatedStreamId, List<Header> headerBlock) throws IOException { - if (closed) throw new IOException("closed"); - headers(outFinished, streamId, headerBlock); - } - - public synchronized void synReply(boolean outFinished, int streamId, - List<Header> headerBlock) throws IOException { - if (closed) throw new IOException("closed"); - headers(outFinished, streamId, headerBlock); - } - - public synchronized void headers(int streamId, List<Header> headerBlock) - throws IOException { - if (closed) throw new IOException("closed"); - headers(false, streamId, headerBlock); - } - public synchronized void rstStream(int streamId, ErrorCode errorCode) throws IOException { if (closed) throw new IOException("closed"); @@ -294,7 +276,8 @@ final class Http2Writer implements Closeable { } } - void headers(boolean outFinished, int streamId, List<Header> headerBlock) throws IOException { + public synchronized void headers( + boolean outFinished, int streamId, List<Header> headerBlock) throws IOException { if (closed) throw new IOException("closed"); hpackWriter.writeHeaders(headerBlock); diff --git a/okhttp/src/main/java/okhttp3/internal/platform/AndroidPlatform.java b/okhttp/src/main/java/okhttp3/internal/platform/AndroidPlatform.java index 08b102c6e06811b6ed12666988c4b85016e5a062..cf184d1fdd68ab5b1ff4528e4a6db8100b93a423 100644 --- a/okhttp/src/main/java/okhttp3/internal/platform/AndroidPlatform.java +++ b/okhttp/src/main/java/okhttp3/internal/platform/AndroidPlatform.java @@ -24,7 +24,6 @@ import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.net.Socket; import java.security.NoSuchAlgorithmException; -import java.security.Security; import java.security.cert.Certificate; import java.security.cert.TrustAnchor; import java.security.cert.X509Certificate; @@ -41,26 +40,25 @@ import okhttp3.internal.tls.BasicTrustRootIndex; import okhttp3.internal.tls.CertificateChainCleaner; import okhttp3.internal.tls.TrustRootIndex; -import static okhttp3.internal.Util.assertionError; +import static java.nio.charset.StandardCharsets.UTF_8; -/** Android 2.3 or better. */ +/** Android 5+. */ class AndroidPlatform extends Platform { private static final int MAX_LOG_LENGTH = 4000; private final Class<?> sslParametersClass; - private final OptionalMethod<Socket> setUseSessionTickets; - private final OptionalMethod<Socket> setHostname; - - // Non-null on Android 5.0+. - private final OptionalMethod<Socket> getAlpnSelectedProtocol; - private final OptionalMethod<Socket> setAlpnProtocols; + private final Class<?> sslSocketClass; + private final Method setUseSessionTickets; + private final Method setHostname; + private final Method getAlpnSelectedProtocol; + private final Method setAlpnProtocols; private final CloseGuard closeGuard = CloseGuard.get(); - AndroidPlatform(Class<?> sslParametersClass, OptionalMethod<Socket> setUseSessionTickets, - OptionalMethod<Socket> setHostname, OptionalMethod<Socket> getAlpnSelectedProtocol, - OptionalMethod<Socket> setAlpnProtocols) { + AndroidPlatform(Class<?> sslParametersClass, Class<?> sslSocketClass, Method setUseSessionTickets, + Method setHostname, Method getAlpnSelectedProtocol, Method setAlpnProtocols) { this.sslParametersClass = sslParametersClass; + this.sslSocketClass = sslSocketClass; this.setUseSessionTickets = setUseSessionTickets; this.setHostname = setHostname; this.getAlpnSelectedProtocol = getAlpnSelectedProtocol; @@ -74,19 +72,11 @@ class AndroidPlatform extends Platform { } catch (AssertionError e) { if (Util.isAndroidGetsocknameError(e)) throw new IOException(e); throw e; - } catch (SecurityException e) { - // Before android 4.3, socket.connect could throw a SecurityException - // if opening a socket resulted in an EACCES error. - IOException ioException = new IOException("Exception in connect"); - ioException.initCause(e); - throw ioException; } catch (ClassCastException e) { // On android 8.0, socket.connect throws a ClassCastException due to a bug // see https://issuetracker.google.com/issues/63649622 if (Build.VERSION.SDK_INT == 26) { - IOException ioException = new IOException("Exception in connect"); - ioException.initCause(e); - throw ioException; + throw new IOException("Exception in connect", e); } else { throw e; } @@ -117,25 +107,34 @@ class AndroidPlatform extends Platform { @Override public void configureTlsExtensions( SSLSocket sslSocket, String hostname, List<Protocol> protocols) { - // Enable SNI and session tickets. - if (hostname != null) { - setUseSessionTickets.invokeOptionalWithoutCheckedException(sslSocket, true); - setHostname.invokeOptionalWithoutCheckedException(sslSocket, hostname); + if (!sslSocketClass.isInstance(sslSocket)) { + return; // No TLS extensions if the socket class is custom. } + try { + // Enable SNI and session tickets. + if (hostname != null) { + setUseSessionTickets.invoke(sslSocket, true); + // This is SSLParameters.setServerNames() in API 24+. + setHostname.invoke(sslSocket, hostname); + } - // Enable ALPN. - if (setAlpnProtocols != null && setAlpnProtocols.isSupported(sslSocket)) { - Object[] parameters = {concatLengthPrefixed(protocols)}; - setAlpnProtocols.invokeWithoutCheckedException(sslSocket, parameters); + // Enable ALPN. + setAlpnProtocols.invoke(sslSocket, concatLengthPrefixed(protocols)); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new AssertionError(e); } } @Override public @Nullable String getSelectedProtocol(SSLSocket socket) { - if (getAlpnSelectedProtocol == null) return null; - if (!getAlpnSelectedProtocol.isSupported(socket)) return null; - - byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invokeWithoutCheckedException(socket); - return alpnResult != null ? new String(alpnResult, Util.UTF_8) : null; + if (!sslSocketClass.isInstance(socket)) { + return null; // No TLS extensions if the socket class is custom. + } + try { + byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invoke(socket); + return alpnResult != null ? new String(alpnResult, UTF_8) : null; + } catch (IllegalAccessException | InvocationTargetException e) { + throw new AssertionError(e); + } } @Override public void log(int level, String message, @Nullable Throwable t) { @@ -175,7 +174,7 @@ class AndroidPlatform extends Platform { } catch (ClassNotFoundException | NoSuchMethodException e) { return super.isCleartextTrafficPermitted(hostname); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw assertionError("unable to determine cleartext support", e); + throw new AssertionError("unable to determine cleartext support", e); } } @@ -201,23 +200,6 @@ class AndroidPlatform extends Platform { } } - /** - * Checks to see if Google Play Services Dynamic Security Provider is present which provides ALPN - * support. If it isn't checks to see if device is Android 5.0+ since 4.x device have broken - * ALPN support. - */ - private static boolean supportsAlpn() { - if (Security.getProvider("GMSCore_OpenSSL") != null) { - return true; - } else { - try { - Class.forName("android.net.Network"); // Arbitrary class added in Android 5.0. - return true; - } catch (ClassNotFoundException ignored) { } - } - return false; - } - public CertificateChainCleaner buildCertificateChainCleaner(X509TrustManager trustManager) { try { Class<?> extensionsClass = Class.forName("android.net.http.X509TrustManagerExtensions"); @@ -231,51 +213,40 @@ class AndroidPlatform extends Platform { } } - public static Platform buildIfSupported() { - // Attempt to find Android 2.3+ APIs. + public static @Nullable Platform buildIfSupported() { + // Attempt to find Android 5+ APIs. + Class<?> sslParametersClass; + Class<?> sslSocketClass; try { - Class<?> sslParametersClass; + sslParametersClass = Class.forName("com.android.org.conscrypt.SSLParametersImpl"); + sslSocketClass = Class.forName("com.android.org.conscrypt.OpenSSLSocketImpl"); + } catch (ClassNotFoundException ignored) { + return null; // Not an Android runtime. + } + if (Build.VERSION.SDK_INT >= 21) { try { - sslParametersClass = Class.forName("com.android.org.conscrypt.SSLParametersImpl"); - } catch (ClassNotFoundException e) { - // Older platform before being unbundled. - sslParametersClass = Class.forName( - "org.apache.harmony.xnet.provider.jsse.SSLParametersImpl"); - } - - OptionalMethod<Socket> setUseSessionTickets = new OptionalMethod<>( - null, "setUseSessionTickets", boolean.class); - OptionalMethod<Socket> setHostname = new OptionalMethod<>( - null, "setHostname", String.class); - OptionalMethod<Socket> getAlpnSelectedProtocol = null; - OptionalMethod<Socket> setAlpnProtocols = null; - - if (supportsAlpn()) { - getAlpnSelectedProtocol - = new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol"); - setAlpnProtocols - = new OptionalMethod<>(null, "setAlpnProtocols", byte[].class); + Method setUseSessionTickets = sslSocketClass.getDeclaredMethod( + "setUseSessionTickets", boolean.class); + Method setHostname = sslSocketClass.getMethod("setHostname", String.class); + Method getAlpnSelectedProtocol = sslSocketClass.getMethod("getAlpnSelectedProtocol"); + Method setAlpnProtocols = sslSocketClass.getMethod("setAlpnProtocols", byte[].class); + return new AndroidPlatform(sslParametersClass, sslSocketClass, setUseSessionTickets, + setHostname, getAlpnSelectedProtocol, setAlpnProtocols); + } catch (NoSuchMethodException ignored) { } - - return new AndroidPlatform(sslParametersClass, setUseSessionTickets, setHostname, - getAlpnSelectedProtocol, setAlpnProtocols); - } catch (ClassNotFoundException ignored) { - // This isn't an Android runtime. } - - return null; + throw new IllegalStateException( + "Expected Android API level 21+ but was " + Build.VERSION.SDK_INT); } - @Override - public TrustRootIndex buildTrustRootIndex(X509TrustManager trustManager) { - + @Override public TrustRootIndex buildTrustRootIndex(X509TrustManager trustManager) { try { // From org.conscrypt.TrustManagerImpl, we want the method with this signature: // private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert); Method method = trustManager.getClass().getDeclaredMethod( - "findTrustAnchorByIssuerAndSignature", X509Certificate.class); + "findTrustAnchorByIssuerAndSignature", X509Certificate.class); method.setAccessible(true); - return new AndroidTrustRootIndex(trustManager, method); + return new CustomTrustRootIndex(trustManager, method); } catch (NoSuchMethodException e) { return super.buildTrustRootIndex(trustManager); } @@ -380,19 +351,17 @@ class AndroidPlatform extends Platform { } /** - * An index of trusted root certificates that exploits knowledge of Android implementation - * details. This class is potentially much faster to initialize than {@link BasicTrustRootIndex} - * because it doesn't need to load and index trusted CA certificates. + * A trust manager for Android applications that customize the trust manager. * - * <p>This class uses APIs added to Android in API 14 (Android 4.0, released October 2011). This - * class shouldn't be used in Android API 17 or better because those releases are better served by - * {@link AndroidPlatform.AndroidCertificateChainCleaner}. + * <p>This class exploits knowledge of Android implementation details. This class is potentially + * much faster to initialize than {@link BasicTrustRootIndex} because it doesn't need to load and + * index trusted CA certificates. */ - static final class AndroidTrustRootIndex implements TrustRootIndex { + static final class CustomTrustRootIndex implements TrustRootIndex { private final X509TrustManager trustManager; private final Method findByIssuerAndSignatureMethod; - AndroidTrustRootIndex(X509TrustManager trustManager, Method findByIssuerAndSignatureMethod) { + CustomTrustRootIndex(X509TrustManager trustManager, Method findByIssuerAndSignatureMethod) { this.findByIssuerAndSignatureMethod = findByIssuerAndSignatureMethod; this.trustManager = trustManager; } @@ -400,32 +369,30 @@ class AndroidPlatform extends Platform { @Override public X509Certificate findByIssuerAndSignature(X509Certificate cert) { try { TrustAnchor trustAnchor = (TrustAnchor) findByIssuerAndSignatureMethod.invoke( - trustManager, cert); + trustManager, cert); return trustAnchor != null - ? trustAnchor.getTrustedCert() - : null; + ? trustAnchor.getTrustedCert() + : null; } catch (IllegalAccessException e) { - throw assertionError("unable to get issues and signature", e); + throw new AssertionError("unable to get issues and signature", e); } catch (InvocationTargetException e) { return null; } } - @Override - public boolean equals(Object obj) { + @Override public boolean equals(Object obj) { if (obj == this) { return true; } - if (!(obj instanceof AndroidTrustRootIndex)) { + if (!(obj instanceof CustomTrustRootIndex)) { return false; } - AndroidTrustRootIndex that = (AndroidTrustRootIndex) obj; + CustomTrustRootIndex that = (CustomTrustRootIndex) obj; return trustManager.equals(that.trustManager) - && findByIssuerAndSignatureMethod.equals(that.findByIssuerAndSignatureMethod); + && findByIssuerAndSignatureMethod.equals(that.findByIssuerAndSignatureMethod); } - @Override - public int hashCode() { + @Override public int hashCode() { return trustManager.hashCode() + 31 * findByIssuerAndSignatureMethod.hashCode(); } } diff --git a/okhttp/src/main/java/okhttp3/internal/platform/JdkWithJettyBootPlatform.java b/okhttp/src/main/java/okhttp3/internal/platform/Jdk8WithJettyBootPlatform.java similarity index 74% rename from okhttp/src/main/java/okhttp3/internal/platform/JdkWithJettyBootPlatform.java rename to okhttp/src/main/java/okhttp3/internal/platform/Jdk8WithJettyBootPlatform.java index dee0b3fe53576ee991683ad616be6f5b92bbb388..64d5096e66c66e5d9c67d602c736ef42678079db 100644 --- a/okhttp/src/main/java/okhttp3/internal/platform/JdkWithJettyBootPlatform.java +++ b/okhttp/src/main/java/okhttp3/internal/platform/Jdk8WithJettyBootPlatform.java @@ -25,19 +25,15 @@ import javax.net.ssl.SSLSocket; import okhttp3.Protocol; import okhttp3.internal.Util; -import static okhttp3.internal.Util.assertionError; - -/** - * OpenJDK 7 or OpenJDK 8 with {@code org.mortbay.jetty.alpn/alpn-boot} in the boot class path. - */ -class JdkWithJettyBootPlatform extends Platform { +/** OpenJDK 8 with {@code org.mortbay.jetty.alpn:alpn-boot} in the boot class path. */ +class Jdk8WithJettyBootPlatform extends Platform { private final Method putMethod; private final Method getMethod; private final Method removeMethod; private final Class<?> clientProviderClass; private final Class<?> serverProviderClass; - JdkWithJettyBootPlatform(Method putMethod, Method getMethod, Method removeMethod, + Jdk8WithJettyBootPlatform(Method putMethod, Method getMethod, Method removeMethod, Class<?> clientProviderClass, Class<?> serverProviderClass) { this.putMethod = putMethod; this.getMethod = getMethod; @@ -51,11 +47,11 @@ class JdkWithJettyBootPlatform extends Platform { List<String> names = alpnProtocolNames(protocols); try { - Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(), - new Class[] {clientProviderClass, serverProviderClass}, new JettyNegoProvider(names)); - putMethod.invoke(null, sslSocket, provider); + Object alpnProvider = Proxy.newProxyInstance(Platform.class.getClassLoader(), + new Class[] {clientProviderClass, serverProviderClass}, new AlpnProvider(names)); + putMethod.invoke(null, sslSocket, alpnProvider); } catch (InvocationTargetException | IllegalAccessException e) { - throw assertionError("unable to set alpn", e); + throw new AssertionError("failed to set ALPN", e); } } @@ -63,14 +59,14 @@ class JdkWithJettyBootPlatform extends Platform { try { removeMethod.invoke(null, sslSocket); } catch (IllegalAccessException | InvocationTargetException e) { - throw assertionError("unable to remove alpn", e); + throw new AssertionError("failed to remove ALPN", e); } } @Override public @Nullable String getSelectedProtocol(SSLSocket socket) { try { - JettyNegoProvider provider = - (JettyNegoProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket)); + AlpnProvider provider = + (AlpnProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket)); if (!provider.unsupported && provider.selected == null) { Platform.get().log(INFO, "ALPN callback dropped: HTTP/2 is disabled. " + "Is alpn-boot on the boot class path?", null); @@ -78,22 +74,22 @@ class JdkWithJettyBootPlatform extends Platform { } return provider.unsupported ? null : provider.selected; } catch (InvocationTargetException | IllegalAccessException e) { - throw assertionError("unable to get selected protocol", e); + throw new AssertionError("failed to get ALPN selected protocol", e); } } public static Platform buildIfSupported() { // Find Jetty's ALPN extension for OpenJDK. try { - String negoClassName = "org.eclipse.jetty.alpn.ALPN"; - Class<?> negoClass = Class.forName(negoClassName); - Class<?> providerClass = Class.forName(negoClassName + "$Provider"); - Class<?> clientProviderClass = Class.forName(negoClassName + "$ClientProvider"); - Class<?> serverProviderClass = Class.forName(negoClassName + "$ServerProvider"); - Method putMethod = negoClass.getMethod("put", SSLSocket.class, providerClass); - Method getMethod = negoClass.getMethod("get", SSLSocket.class); - Method removeMethod = negoClass.getMethod("remove", SSLSocket.class); - return new JdkWithJettyBootPlatform( + String alpnClassName = "org.eclipse.jetty.alpn.ALPN"; + Class<?> alpnClass = Class.forName(alpnClassName); + Class<?> providerClass = Class.forName(alpnClassName + "$Provider"); + Class<?> clientProviderClass = Class.forName(alpnClassName + "$ClientProvider"); + Class<?> serverProviderClass = Class.forName(alpnClassName + "$ServerProvider"); + Method putMethod = alpnClass.getMethod("put", SSLSocket.class, providerClass); + Method getMethod = alpnClass.getMethod("get", SSLSocket.class); + Method removeMethod = alpnClass.getMethod("remove", SSLSocket.class); + return new Jdk8WithJettyBootPlatform( putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass); } catch (ClassNotFoundException | NoSuchMethodException ignored) { } @@ -105,7 +101,7 @@ class JdkWithJettyBootPlatform extends Platform { * Handle the methods of ALPN's ClientProvider and ServerProvider without a compile-time * dependency on those interfaces. */ - private static class JettyNegoProvider implements InvocationHandler { + private static class AlpnProvider implements InvocationHandler { /** This peer's supported protocols. */ private final List<String> protocols; /** Set when remote peer notifies ALPN is unsupported. */ @@ -113,7 +109,7 @@ class JdkWithJettyBootPlatform extends Platform { /** The protocol the server selected. */ String selected; - JettyNegoProvider(List<String> protocols) { + AlpnProvider(List<String> protocols) { this.protocols = protocols; } @@ -132,11 +128,12 @@ class JdkWithJettyBootPlatform extends Platform { return protocols; // Client advertises these protocols. } else if ((methodName.equals("selectProtocol") || methodName.equals("select")) && String.class == returnType && args.length == 1 && args[0] instanceof List) { - List<String> peerProtocols = (List) args[0]; + List<?> peerProtocols = (List) args[0]; // Pick the first known protocol the peer advertises. for (int i = 0, size = peerProtocols.size(); i < size; i++) { - if (protocols.contains(peerProtocols.get(i))) { - return selected = peerProtocols.get(i); + String protocol = (String) peerProtocols.get(i); + if (protocols.contains(protocol)) { + return selected = protocol; } } return selected = protocols.get(0); // On no intersection, try peer's first protocol. diff --git a/okhttp/src/main/java/okhttp3/internal/platform/Jdk9Platform.java b/okhttp/src/main/java/okhttp3/internal/platform/Jdk9Platform.java index 455e8bcbb8dc0be5024c12ed073630ced9e5f12d..07cb67c88538fc7c37ce176c08cd6d2e94a4b749 100644 --- a/okhttp/src/main/java/okhttp3/internal/platform/Jdk9Platform.java +++ b/okhttp/src/main/java/okhttp3/internal/platform/Jdk9Platform.java @@ -25,11 +25,7 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; import okhttp3.Protocol; -import static okhttp3.internal.Util.assertionError; - -/** - * OpenJDK 9+. - */ +/** OpenJDK 9+. */ final class Jdk9Platform extends Platform { final Method setProtocolMethod; final Method getProtocolMethod; @@ -52,7 +48,7 @@ final class Jdk9Platform extends Platform { sslSocket.setSSLParameters(sslParameters); } catch (IllegalAccessException | InvocationTargetException e) { - throw assertionError("unable to set ssl parameters", e); + throw new AssertionError("failed to set SSL parameters", e); } } @@ -69,7 +65,7 @@ final class Jdk9Platform extends Platform { return protocol; } catch (IllegalAccessException | InvocationTargetException e) { - throw assertionError("unable to get selected protocols", e); + throw new AssertionError("failed to get ALPN selected protocol", e); } } diff --git a/okhttp/src/main/java/okhttp3/internal/platform/OptionalMethod.java b/okhttp/src/main/java/okhttp3/internal/platform/OptionalMethod.java deleted file mode 100644 index c26132fedb777b2efeffa1a9be9d9fa272823742..0000000000000000000000000000000000000000 --- a/okhttp/src/main/java/okhttp3/internal/platform/OptionalMethod.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package okhttp3.internal.platform; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; - -/** - * Duck-typing for methods: Represents a method that may or may not be present on an object. - * - * @param <T> the type of the object the method might be on, typically an interface or base class - */ -class OptionalMethod<T> { - - /** The return type of the method. null means "don't care". */ - private final Class<?> returnType; - - private final String methodName; - - private final Class[] methodParams; - - /** - * Creates an optional method. - * - * @param returnType the return type to required, null if it does not matter - * @param methodName the name of the method - * @param methodParams the method parameter types - */ - OptionalMethod(Class<?> returnType, String methodName, Class... methodParams) { - this.returnType = returnType; - this.methodName = methodName; - this.methodParams = methodParams; - } - - /** - * Returns true if the method exists on the supplied {@code target}. - */ - public boolean isSupported(T target) { - return getMethod(target.getClass()) != null; - } - - /** - * Invokes the method on {@code target} with {@code args}. If the method does not exist or is not - * public then {@code null} is returned. See also {@link #invokeOptionalWithoutCheckedException}. - * - * @throws IllegalArgumentException if the arguments are invalid - * @throws InvocationTargetException if the invocation throws an exception - */ - public Object invokeOptional(T target, Object... args) throws InvocationTargetException { - Method m = getMethod(target.getClass()); - if (m == null) { - return null; - } - try { - return m.invoke(target, args); - } catch (IllegalAccessException e) { - return null; - } - } - - /** - * Invokes the method on {@code target}. If the method does not exist or is not public then - * {@code null} is returned. Any RuntimeException thrown by the method is thrown, checked - * exceptions are wrapped in an {@link AssertionError}. - * - * @throws IllegalArgumentException if the arguments are invalid - */ - public Object invokeOptionalWithoutCheckedException(T target, Object... args) { - try { - return invokeOptional(target, args); - } catch (InvocationTargetException e) { - Throwable targetException = e.getTargetException(); - if (targetException instanceof RuntimeException) { - throw (RuntimeException) targetException; - } - AssertionError error = new AssertionError("Unexpected exception"); - error.initCause(targetException); - throw error; - } - } - - /** - * Invokes the method on {@code target} with {@code args}. Throws an error if the method is not - * supported. See also {@link #invokeWithoutCheckedException(Object, Object...)}. - * - * @throws IllegalArgumentException if the arguments are invalid - * @throws InvocationTargetException if the invocation throws an exception - */ - public Object invoke(T target, Object... args) throws InvocationTargetException { - Method m = getMethod(target.getClass()); - if (m == null) { - throw new AssertionError("Method " + methodName + " not supported for object " + target); - } - try { - return m.invoke(target, args); - } catch (IllegalAccessException e) { - // Method should be public: we checked. - AssertionError error = new AssertionError("Unexpectedly could not call: " + m); - error.initCause(e); - throw error; - } - } - - /** - * Invokes the method on {@code target}. Throws an error if the method is not supported. Any - * RuntimeException thrown by the method is thrown, checked exceptions are wrapped in an {@link - * AssertionError}. - * - * @throws IllegalArgumentException if the arguments are invalid - */ - public Object invokeWithoutCheckedException(T target, Object... args) { - try { - return invoke(target, args); - } catch (InvocationTargetException e) { - Throwable targetException = e.getTargetException(); - if (targetException instanceof RuntimeException) { - throw (RuntimeException) targetException; - } - AssertionError error = new AssertionError("Unexpected exception"); - error.initCause(targetException); - throw error; - } - } - - /** - * Perform a lookup for the method. No caching. In order to return a method the method name and - * arguments must match those specified when the {@link OptionalMethod} was created. If the return - * type is specified (i.e. non-null) it must also be compatible. The method must also be public. - */ - private Method getMethod(Class<?> clazz) { - Method method = null; - if (methodName != null) { - method = getPublicMethod(clazz, methodName, methodParams); - if (method != null - && returnType != null - && !returnType.isAssignableFrom(method.getReturnType())) { - - // If the return type is non-null it must be compatible. - method = null; - } - } - return method; - } - - private static Method getPublicMethod(Class<?> clazz, String methodName, Class[] parameterTypes) { - Method method = null; - try { - method = clazz.getMethod(methodName, parameterTypes); - if ((method.getModifiers() & Modifier.PUBLIC) == 0) { - method = null; - } - } catch (NoSuchMethodException e) { - // None. - } - return method; - } -} - diff --git a/okhttp/src/main/java/okhttp3/internal/platform/Platform.java b/okhttp/src/main/java/okhttp3/internal/platform/Platform.java index ac152338677f8d3ea5a5ee8a2808b7c812c1e322..fdfd244acc2c4dfdda7dc666e999dca0d859ee0c 100644 --- a/okhttp/src/main/java/okhttp3/internal/platform/Platform.java +++ b/okhttp/src/main/java/okhttp3/internal/platform/Platform.java @@ -33,6 +33,7 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; import okhttp3.OkHttpClient; import okhttp3.Protocol; +import okhttp3.internal.Util; import okhttp3.internal.tls.BasicCertificateChainCleaner; import okhttp3.internal.tls.BasicTrustRootIndex; import okhttp3.internal.tls.CertificateChainCleaner; @@ -46,7 +47,7 @@ import okio.Buffer; * * <p>Supported on Android 2.3+. * - * Supported on OpenJDK 7+ + * <p>Supported on OpenJDK 7+ * * <h3>Session Tickets</h3> * @@ -61,9 +62,9 @@ import okio.Buffer; * <p>Supported on Android 5.0+. The APIs were present in Android 4.4, but that implementation was * unstable. * - * Supported on OpenJDK 7 and 8 (via the JettyALPN-boot library). + * <p>Supported on OpenJDK 8 via the JettyALPN-boot library. * - * Supported on OpenJDK 9 via SSLParameters and SSLSocket features. + * <p>Supported on OpenJDK 9+ via SSLParameters and SSLSocket features. * * <h3>Trust Manager Extraction</h3> * @@ -187,7 +188,7 @@ public class Platform { public static boolean isConscryptPreferred() { // mainly to allow tests to run cleanly - if ("conscrypt".equals(System.getProperty("okhttp.platform"))) { + if ("conscrypt".equals(Util.getSystemProperty("okhttp.platform", null))) { return true; } @@ -218,7 +219,7 @@ public class Platform { return jdk9; } - Platform jdkWithJettyBoot = JdkWithJettyBootPlatform.buildIfSupported(); + Platform jdkWithJettyBoot = Jdk8WithJettyBootPlatform.buildIfSupported(); if (jdkWithJettyBoot != null) { return jdkWithJettyBoot; @@ -249,7 +250,7 @@ public class Platform { Field field = c.getDeclaredField(fieldName); field.setAccessible(true); Object value = field.get(instance); - if (value == null || !fieldType.isInstance(value)) return null; + if (!fieldType.isInstance(value)) return null; return fieldType.cast(value); } catch (NoSuchFieldException ignored) { } catch (IllegalAccessException e) { @@ -267,16 +268,6 @@ public class Platform { } public SSLContext getSSLContext() { - String jvmVersion = System.getProperty("java.specification.version"); - if ("1.7".equals(jvmVersion)) { - try { - // JDK 1.7 (public version) only support > TLSv1 with named protocols - return SSLContext.getInstance("TLSv1.2"); - } catch (NoSuchAlgorithmException e) { - // fallback to TLS - } - } - try { return SSLContext.getInstance("TLS"); } catch (NoSuchAlgorithmException e) { diff --git a/okhttp/src/main/java/okhttp3/internal/publicsuffix/PublicSuffixDatabase.java b/okhttp/src/main/java/okhttp3/internal/publicsuffix/PublicSuffixDatabase.java index 0e39bd0f876512b091f397aa5298916af47f3c4c..74fbd89b79988b6406ddd395690a6ee819559294 100644 --- a/okhttp/src/main/java/okhttp3/internal/publicsuffix/PublicSuffixDatabase.java +++ b/okhttp/src/main/java/okhttp3/internal/publicsuffix/PublicSuffixDatabase.java @@ -21,13 +21,12 @@ import java.io.InterruptedIOException; import java.net.IDN; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; -import okhttp3.internal.Util; import okhttp3.internal.platform.Platform; import okio.BufferedSource; import okio.GzipSource; import okio.Okio; -import static okhttp3.internal.Util.closeQuietly; +import static java.nio.charset.StandardCharsets.UTF_8; /** * A database of public suffixes provided by @@ -128,7 +127,7 @@ public final class PublicSuffixDatabase { // Break apart the domain into UTF-8 labels, i.e. foo.bar.com turns into [foo, bar, com]. byte[][] domainLabelsUtf8Bytes = new byte[domainLabels.length][]; for (int i = 0; i < domainLabels.length; i++) { - domainLabelsUtf8Bytes[i] = domainLabels[i].getBytes(Util.UTF_8); + domainLabelsUtf8Bytes[i] = domainLabels[i].getBytes(UTF_8); } // Start by looking for exact matches. We start at the leftmost label. For example, foo.bar.com @@ -271,7 +270,7 @@ public final class PublicSuffixDatabase { low = mid + end + 1; } else { // Found a match. - match = new String(bytesToSearch, mid, publicSuffixLength, Util.UTF_8); + match = new String(bytesToSearch, mid, publicSuffixLength, UTF_8); break; } } @@ -313,8 +312,7 @@ public final class PublicSuffixDatabase { InputStream resource = PublicSuffixDatabase.class.getResourceAsStream(PUBLIC_SUFFIX_RESOURCE); if (resource == null) return; - BufferedSource bufferedSource = Okio.buffer(new GzipSource(Okio.source(resource))); - try { + try (BufferedSource bufferedSource = Okio.buffer(new GzipSource(Okio.source(resource)))) { int totalBytes = bufferedSource.readInt(); publicSuffixListBytes = new byte[totalBytes]; bufferedSource.readFully(publicSuffixListBytes); @@ -322,8 +320,6 @@ public final class PublicSuffixDatabase { int totalExceptionBytes = bufferedSource.readInt(); publicSuffixExceptionListBytes = new byte[totalExceptionBytes]; bufferedSource.readFully(publicSuffixExceptionListBytes); - } finally { - closeQuietly(bufferedSource); } synchronized (this) { diff --git a/okhttp/src/main/java/okhttp3/internal/ws/RealWebSocket.java b/okhttp/src/main/java/okhttp3/internal/ws/RealWebSocket.java index baee049f1bc96df6124edeb75678cde8954ab23a..61a0d27e5e4cbca59b3ad97d720a37ef646f89bd 100644 --- a/okhttp/src/main/java/okhttp3/internal/ws/RealWebSocket.java +++ b/okhttp/src/main/java/okhttp3/internal/ws/RealWebSocket.java @@ -153,14 +153,12 @@ public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCall random.nextBytes(nonce); this.key = ByteString.of(nonce).base64(); - this.writerRunnable = new Runnable() { - @Override public void run() { - try { - while (writeOneFrame()) { - } - } catch (IOException e) { - failWebSocket(e, null); + this.writerRunnable = () -> { + try { + while (writeOneFrame()) { } + } catch (IOException e) { + failWebSocket(e, null); } }; } @@ -192,24 +190,26 @@ public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCall call.timeout().clearTimeout(); call.enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { + StreamAllocation streamAllocation = Internal.instance.streamAllocation(call); + try { checkResponse(response); } catch (ProtocolException e) { failWebSocket(e, response); closeQuietly(response); + streamAllocation.streamFailed(e); return; } // Promote the HTTP streams into web socket streams. - StreamAllocation streamAllocation = Internal.instance.streamAllocation(call); streamAllocation.noNewStreams(); // Prevent connection pooling! Streams streams = streamAllocation.connection().newWebSocketStreams(streamAllocation); // Process all web socket messages. try { - listener.onOpen(RealWebSocket.this, response); String name = "OkHttp WebSocket " + request.url().redact(); initReaderAndWriter(name, streams); + listener.onOpen(RealWebSocket.this, response); streamAllocation.connection().socket().setSoTimeout(0); loopReader(); } catch (Exception e) { diff --git a/okhttp/src/main/java/okhttp3/internal/ws/WebSocketWriter.java b/okhttp/src/main/java/okhttp3/internal/ws/WebSocketWriter.java index baffe06b198b53682f7797eef5ca2a54ba79eae1..3ba1204987d38ae5aefad42c58259a43f3226ff0 100644 --- a/okhttp/src/main/java/okhttp3/internal/ws/WebSocketWriter.java +++ b/okhttp/src/main/java/okhttp3/internal/ws/WebSocketWriter.java @@ -246,7 +246,6 @@ final class WebSocketWriter { return sink.timeout(); } - @SuppressWarnings("PointlessBitwiseExpression") @Override public void close() throws IOException { if (closed) throw new IOException("closed"); diff --git a/pom.xml b/pom.xml index ffd392c06a89ba026c27862edf2369b795f3a111..6238941bf6d8eddfebda011c1192ae5e18ebdf48 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ <groupId>com.squareup.okhttp3</groupId> <artifactId>parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> <packaging>pom</packaging> <name>OkHttp (Parent)</name> @@ -36,28 +36,24 @@ <module>okcurl</module> <module>mockwebserver</module> - <module>bom</module> <module>samples</module> - <module>benchmarks</module> </modules> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- Compilation --> - <airlift.version>0.7</airlift.version> - <!-- ALPN library targeted to Java 7 --> - <alpn.jdk7.version>7.1.2.v20141202</alpn.jdk7.version> + <airlift.version>0.8</airlift.version> <android.version>4.1.1.4</android.version> - <animal.sniffer.version>1.15</animal.sniffer.version> - <apache.http.version>4.2.6</apache.http.version> + <animal.sniffer.version>1.17</animal.sniffer.version> + <apache.http.version>4.5.6</apache.http.version> <bouncycastle.version>1.60</bouncycastle.version> - <guava.version>16.0</guava.version> - <java.version>1.7</java.version> - <moshi.version>1.1.0</moshi.version> - <jnr-unixsocket.version>0.19</jnr-unixsocket.version> - <okio.version>1.15.0</okio.version> - <conscrypt.version>1.4.0</conscrypt.version> + <guava.version>27.0.1-jre</guava.version> + <java.version>1.8</java.version> + <moshi.version>1.8.0</moshi.version> + <jnr-unixsocket.version>0.21</jnr-unixsocket.version> + <okio.version>1.17.2</okio.version> + <conscrypt.version>1.4.2</conscrypt.version> <!-- Test Dependencies --> <junit.version>4.12</junit.version> @@ -70,7 +66,7 @@ <url>https://github.com/square/okhttp/</url> <connection>scm:git:https://github.com/square/okhttp.git</connection> <developerConnection>scm:git:git@github.com:square/okhttp.git</developerConnection> - <tag>parent-3.12.1</tag> + <tag>parent-3.13.1</tag> </scm> <issueManagement> @@ -152,7 +148,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.7.0</version> + <version>3.8.0</version> <configuration> <compilerId>javac-with-errorprone</compilerId> <forceJavacCompilerUse>true</forceJavacCompilerUse> @@ -163,12 +159,12 @@ <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-compiler-javac-errorprone</artifactId> - <version>2.8.4</version> + <version>2.8.5</version> </dependency> <dependency> <groupId>com.google.errorprone</groupId> <artifactId>error_prone_core</artifactId> - <version>2.3.1</version> + <version>2.3.2</version> </dependency> </dependencies> </plugin> @@ -176,7 +172,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> - <version>2.21.0</version> + <version>2.22.1</version> <configuration> <systemPropertyVariables> <okhttp.platform>${okhttp.platform}</okhttp.platform> @@ -198,7 +194,7 @@ <dependency> <groupId>org.apache.maven.surefire</groupId> <artifactId>surefire-junit47</artifactId> - <version>2.21.0</version> + <version>2.22.1</version> </dependency> </dependencies> </plugin> @@ -206,7 +202,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>2.10.4</version> + <version>3.0.1</version> </plugin> </plugins> </pluginManagement> @@ -215,14 +211,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-release-plugin</artifactId> - <version>2.4.2</version> - <dependencies> - <dependency> - <groupId>org.apache.maven.scm</groupId> - <artifactId>maven-scm-provider-gitexe</artifactId> - <version>1.9</version> - </dependency> - </dependencies> + <version>2.5.3</version> <configuration> <autoVersionSubmodules>true</autoVersionSubmodules> </configuration> @@ -231,12 +220,12 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> - <version>2.17</version> + <version>3.0.0</version> <dependencies> <dependency> <groupId>com.puppycrawl.tools</groupId> <artifactId>checkstyle</artifactId> - <version>7.7</version> + <version>8.15</version> </dependency> </dependencies> <configuration> @@ -260,56 +249,39 @@ <version>${animal.sniffer.version}</version> <executions> <execution> + <id>sniff-java18</id> + <phase>test</phase> + <goals> + <goal>check</goal> + </goals> + <configuration> + <signature> + <groupId>org.codehaus.mojo.signature</groupId> + <artifactId>java18</artifactId> + <version>1.0</version> + </signature> + </configuration> + </execution> + <execution> + <id>sniff-android5</id> <phase>test</phase> <goals> <goal>check</goal> </goals> + <configuration> + <signature> + <groupId>net.sf.androidscents.signature</groupId> + <artifactId>android-api-level-21</artifactId> + <version>5.0.1_r2</version> + </signature> + </configuration> </execution> </executions> - <configuration> - <signature> - <groupId>org.codehaus.mojo.signature</groupId> - <artifactId>java16</artifactId> - <version>1.1</version> - </signature> - </configuration> </plugin> </plugins> </build> <profiles> - <profile> - <id>alpn-when-jdk7</id> - <activation> - <jdk>1.7</jdk> - </activation> - <properties> - <bootclasspathPrefix> - ${settings.localRepository}/org/mortbay/jetty/alpn/alpn-boot/${alpn.jdk7.version}/alpn-boot-${alpn.jdk7.version}.jar - </bootclasspathPrefix> - <okhttp.platform>jdk-with-jetty-boot</okhttp.platform> - </properties> - <build> - <pluginManagement> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-surefire-plugin</artifactId> - <configuration> - <argLine>-Xbootclasspath/p:${bootclasspathPrefix} -Xms512m -Xmx512m</argLine> - </configuration> - <dependencies> - <dependency> - <groupId>org.mortbay.jetty.alpn</groupId> - <artifactId>alpn-boot</artifactId> - <version>${alpn.jdk7.version}</version> - </dependency> - </dependencies> - </plugin> - </plugins> - </pluginManagement> - </build> - </profile> <profile> <id>alpn-when-jdk8</id> <activation> @@ -681,5 +653,23 @@ <alpn.jdk8.version>8.1.13.v20181017</alpn.jdk8.version> </properties> </profile> + <profile> + <id>alpn-when-jdk8_201</id> + <activation> + <jdk>1.8.0_201</jdk> + </activation> + <properties> + <alpn.jdk8.version>8.1.13.v20181017</alpn.jdk8.version> + </properties> + </profile> + <profile> + <id>alpn-when-jdk8_202</id> + <activation> + <jdk>1.8.0_202</jdk> + </activation> + <properties> + <alpn.jdk8.version>8.1.13.v20181017</alpn.jdk8.version> + </properties> + </profile> </profiles> </project> diff --git a/samples/crawler/pom.xml b/samples/crawler/pom.xml index 1744c10773cc8c5864a9a986fd5d45dd555fe1bb..68884ca5a8a9d22ecd53a8714acf76ff5856aa4c 100644 --- a/samples/crawler/pom.xml +++ b/samples/crawler/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3.sample</groupId> <artifactId>sample-parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>crawler</artifactId> @@ -21,7 +21,7 @@ <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.7.3</version> + <version>1.11.3</version> </dependency> </dependencies> </project> diff --git a/samples/crawler/src/main/java/okhttp3/sample/Crawler.java b/samples/crawler/src/main/java/okhttp3/sample/Crawler.java index 07f61032d31d8a22244e805529e14f9cb279716a..95ba357def6fad03cde343ce2c6faa38c33d058d 100644 --- a/samples/crawler/src/main/java/okhttp3/sample/Crawler.java +++ b/samples/crawler/src/main/java/okhttp3/sample/Crawler.java @@ -41,8 +41,7 @@ import org.jsoup.nodes.Element; */ public final class Crawler { private final OkHttpClient client; - private final Set<HttpUrl> fetchedUrls = Collections.synchronizedSet( - new LinkedHashSet<HttpUrl>()); + private final Set<HttpUrl> fetchedUrls = Collections.synchronizedSet(new LinkedHashSet<>()); private final LinkedBlockingQueue<HttpUrl> queue = new LinkedBlockingQueue<>(); private final ConcurrentHashMap<String, AtomicInteger> hostnames = new ConcurrentHashMap<>(); diff --git a/samples/guide/pom.xml b/samples/guide/pom.xml index ec79832110d519e81a9609a7a1d58ee1c00accbb..86adfce40d34763c519164427d65e7b247ed703d 100644 --- a/samples/guide/pom.xml +++ b/samples/guide/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3.sample</groupId> <artifactId>sample-parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>guide</artifactId> diff --git a/samples/guide/src/main/java/okhttp3/recipes/CancelCall.java b/samples/guide/src/main/java/okhttp3/recipes/CancelCall.java index 644f638300fe3f15385b11ec3d10d62c6223b534..58a791adc729cdd720dcb8ddda1e10c749b082b2 100644 --- a/samples/guide/src/main/java/okhttp3/recipes/CancelCall.java +++ b/samples/guide/src/main/java/okhttp3/recipes/CancelCall.java @@ -37,12 +37,10 @@ public class CancelCall { final Call call = client.newCall(request); // Schedule a job to cancel the call in 1 second. - executor.schedule(new Runnable() { - @Override public void run() { - System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f); - call.cancel(); - System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f); - } + executor.schedule(() -> { + System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f); + call.cancel(); + System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f); }, 1, TimeUnit.SECONDS); System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f); diff --git a/samples/guide/src/main/java/okhttp3/recipes/Progress.java b/samples/guide/src/main/java/okhttp3/recipes/Progress.java index e75a3cc9495b287103b0fef943de9a9a1d01c7ee..197401f205fa51041921c36867bfd1143216d592 100644 --- a/samples/guide/src/main/java/okhttp3/recipes/Progress.java +++ b/samples/guide/src/main/java/okhttp3/recipes/Progress.java @@ -16,7 +16,6 @@ package okhttp3.recipes; import java.io.IOException; -import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -61,13 +60,11 @@ public final class Progress { }; OkHttpClient client = new OkHttpClient.Builder() - .addNetworkInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - Response originalResponse = chain.proceed(chain.request()); - return originalResponse.newBuilder() - .body(new ProgressResponseBody(originalResponse.body(), progressListener)) - .build(); - } + .addNetworkInterceptor(chain -> { + Response originalResponse = chain.proceed(chain.request()); + return originalResponse.newBuilder() + .body(new ProgressResponseBody(originalResponse.body(), progressListener)) + .build(); }) .build(); diff --git a/samples/guide/src/main/java/okhttp3/recipes/RewriteResponseCacheControl.java b/samples/guide/src/main/java/okhttp3/recipes/RewriteResponseCacheControl.java index 92d65d8169b43842335a517ee871795b58177b02..4d876bb201f68a2dab7faa8cac8e59f6f8842e70 100644 --- a/samples/guide/src/main/java/okhttp3/recipes/RewriteResponseCacheControl.java +++ b/samples/guide/src/main/java/okhttp3/recipes/RewriteResponseCacheControl.java @@ -25,13 +25,11 @@ import okhttp3.Response; public final class RewriteResponseCacheControl { /** Dangerous interceptor that rewrites the server's cache-control header. */ - private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { - Response originalResponse = chain.proceed(chain.request()); - return originalResponse.newBuilder() - .header("Cache-Control", "max-age=60") - .build(); - } + private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = chain -> { + Response originalResponse = chain.proceed(chain.request()); + return originalResponse.newBuilder() + .header("Cache-Control", "max-age=60") + .build(); }; private final OkHttpClient client; diff --git a/samples/pom.xml b/samples/pom.xml index eb8a78bbe07f409d85f1fec91a69c7f14328af8c..63f76a59952566d49a29322692b9e2449bc6b5c2 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3</groupId> <artifactId>parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <groupId>com.squareup.okhttp3.sample</groupId> diff --git a/samples/simple-client/pom.xml b/samples/simple-client/pom.xml index 223317fbde22a86b1040969d31a9a00371a338bc..44e1c926675e156a6ed683dd4e9732c5773e40c6 100644 --- a/samples/simple-client/pom.xml +++ b/samples/simple-client/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3.sample</groupId> <artifactId>sample-parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>simple-client</artifactId> diff --git a/samples/simple-client/src/main/java/okhttp3/sample/OkHttpContributors.java b/samples/simple-client/src/main/java/okhttp3/sample/OkHttpContributors.java index f2ac973db2b89c40db80230d18d4d4a9658dd65a..cfd8632cb86ec8fdd22a66874b0ae3f5854c2ae0 100644 --- a/samples/simple-client/src/main/java/okhttp3/sample/OkHttpContributors.java +++ b/samples/simple-client/src/main/java/okhttp3/sample/OkHttpContributors.java @@ -4,7 +4,6 @@ import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; import com.squareup.moshi.Types; import java.util.Collections; -import java.util.Comparator; import java.util.List; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -37,11 +36,7 @@ public class OkHttpContributors { List<Contributor> contributors = CONTRIBUTORS_JSON_ADAPTER.fromJson(body.source()); // Sort list by the most contributions. - Collections.sort(contributors, new Comparator<Contributor>() { - @Override public int compare(Contributor c1, Contributor c2) { - return c2.contributions - c1.contributions; - } - }); + Collections.sort(contributors, (c1, c2) -> c2.contributions - c1.contributions); // Output list of contributors. for (Contributor contributor : contributors) { diff --git a/samples/slack/pom.xml b/samples/slack/pom.xml index d2c65164232421872df259c0d5c1a4c62a91fcba..bf946d4e3f521622c10224e8d7466f2481b3ccb6 100644 --- a/samples/slack/pom.xml +++ b/samples/slack/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3.sample</groupId> <artifactId>sample-parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>slack</artifactId> diff --git a/samples/slack/src/main/java/okhttp3/slack/SlackClient.java b/samples/slack/src/main/java/okhttp3/slack/SlackClient.java index 2aa26089866a0f7b3ba8535c9413a2c4d54fcbb3..508a5fd46775b464ba9cd865ad4310ec379f2034 100644 --- a/samples/slack/src/main/java/okhttp3/slack/SlackClient.java +++ b/samples/slack/src/main/java/okhttp3/slack/SlackClient.java @@ -39,13 +39,10 @@ public final class SlackClient { sessionFactory.start(); } - HttpUrl authorizeUrl = sessionFactory.newAuthorizeUrl(scopes, team, - new OAuthSessionFactory.Listener() { - @Override public void sessionGranted(OAuthSession session) { - initOauthSession(session); - System.out.printf("session granted: %s\n", session); - } - }); + HttpUrl authorizeUrl = sessionFactory.newAuthorizeUrl(scopes, team, session -> { + initOauthSession(session); + System.out.printf("session granted: %s\n", session); + }); System.out.printf("open this URL in a browser: %s\n", authorizeUrl); } diff --git a/samples/static-server/pom.xml b/samples/static-server/pom.xml index 57b03ae7f386cff18822d9a17f7e05b9a0618bdc..a160f9f46e68f3caae91889039077841dcca712b 100644 --- a/samples/static-server/pom.xml +++ b/samples/static-server/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3.sample</groupId> <artifactId>sample-parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>static-server</artifactId> @@ -26,7 +26,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> - <version>2.1</version> + <version>3.2.1</version> <configuration> <shadedArtifactAttached>true</shadedArtifactAttached> <shadedClassifierName>shaded</shadedClassifierName> diff --git a/samples/static-server/src/main/java/okhttp3/sample/SampleServer.java b/samples/static-server/src/main/java/okhttp3/sample/SampleServer.java index e21432ed20d05971d6e536ad39b977007393fd0d..88e0251bdb68d9e2c9fdd2a7990145850a98cd0d 100644 --- a/samples/static-server/src/main/java/okhttp3/sample/SampleServer.java +++ b/samples/static-server/src/main/java/okhttp3/sample/SampleServer.java @@ -11,7 +11,6 @@ import java.security.SecureRandom; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; -import okhttp3.internal.Util; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; @@ -119,11 +118,8 @@ public class SampleServer extends Dispatcher { private static SSLContext sslContext(String keystoreFile, String password) throws GeneralSecurityException, IOException { KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); - InputStream in = new FileInputStream(keystoreFile); - try { + try (InputStream in = new FileInputStream(keystoreFile)) { keystore.load(in, password.toCharArray()); - } finally { - Util.closeQuietly(in); } KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); diff --git a/samples/unixdomainsockets/pom.xml b/samples/unixdomainsockets/pom.xml index 756a254afe9157a4110eca25ff2e8fbeafafd800..07a0c3c953b9412c29839ded8ea5d428853a14b5 100644 --- a/samples/unixdomainsockets/pom.xml +++ b/samples/unixdomainsockets/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>com.squareup.okhttp3.sample</groupId> <artifactId>sample-parent</artifactId> - <version>3.12.1</version> + <version>3.13.1</version> </parent> <artifactId>unixdomainsockets</artifactId> diff --git a/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/BlockingUnixSocket.java b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/BlockingUnixSocket.java new file mode 100644 index 0000000000000000000000000000000000000000..3ba58448c2bd3284e6e0a57d7bd23b22b52d59c6 --- /dev/null +++ b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/BlockingUnixSocket.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2018 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package okhttp3.unixdomainsockets; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.WritableByteChannel; +import jnr.unixsocket.UnixSocket; +import jnr.unixsocket.UnixSocketAddress; +import jnr.unixsocket.UnixSocketChannel; + +/** + * Subtype UNIX socket for a higher-fidelity impersonation of TCP sockets. + * + * <p>This class doesn't pass {@link SelectableChannel} implementations to create input and output + * streams. Though that type isn't in the public API, if the channel passed in implements that + * interface then additional synchronization is used. This additional synchronization harms + * concurrency and can cause deadlocks. + * + * <p>This class remembers which socket address was connected so that a non-null value can be + * returned on calls to {@link #getInetAddress}. + */ +final class BlockingUnixSocket extends UnixSocket { + private final File path; + private final InputStream in; + private final OutputStream out; + private InetSocketAddress inetSocketAddress; + + BlockingUnixSocket(File path, UnixSocketChannel channel) { + super(channel); + this.path = path; + this.in = Channels.newInputStream(new UnselectableReadableByteChannel()); + this.out = Channels.newOutputStream(new UnselectableWritableByteChannel()); + } + + BlockingUnixSocket(File path, UnixSocketChannel channel, InetSocketAddress address) { + this(path, channel); + this.inetSocketAddress = address; + } + + @Override public void connect(SocketAddress endpoint) throws IOException { + connect(endpoint, Integer.valueOf(0)); + } + + @Override public void connect(SocketAddress endpoint, int timeout) throws IOException { + connect(endpoint, Integer.valueOf(timeout)); + } + + @Override public void connect(SocketAddress endpoint, Integer timeout) throws IOException { + this.inetSocketAddress = (InetSocketAddress) endpoint; + super.connect(new UnixSocketAddress(path), timeout); + } + + @Override public InetAddress getInetAddress() { + return inetSocketAddress.getAddress(); // TODO(jwilson): fake the remote address? + } + + @Override public InputStream getInputStream() throws IOException { + if (!isConnected()) throw new IOException("not connected"); + return in; + } + + @Override public OutputStream getOutputStream() throws IOException { + if (!isConnected()) throw new IOException("not connected"); + return out; + } + + /** A readable byte channel that doesn't implement {@link SelectableChannel}. */ + final class UnselectableReadableByteChannel implements ReadableByteChannel { + @Override public int read(ByteBuffer dst) throws IOException { + return getChannel().read(dst); + } + + @Override public boolean isOpen() { + return getChannel().isOpen(); + } + + @Override public void close() throws IOException { + getChannel().close(); + } + } + + /** A writable byte channel that doesn't implement {@link SelectableChannel}. */ + final class UnselectableWritableByteChannel implements WritableByteChannel { + @Override public int write(ByteBuffer src) throws IOException { + return getChannel().write(src); + } + + @Override public boolean isOpen() { + return getChannel().isOpen(); + } + + @Override public void close() throws IOException { + getChannel().close(); + } + } +} diff --git a/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/ClientAndServer.java b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/ClientAndServer.java index 8512c2d38d070d3e2282da7278335871332c451d..f897565f52389cb906444da1dd2151ee6c941187 100644 --- a/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/ClientAndServer.java +++ b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/ClientAndServer.java @@ -16,7 +16,9 @@ package okhttp3.unixdomainsockets; import java.io.File; +import java.util.Collections; import okhttp3.OkHttpClient; +import okhttp3.Protocol; import okhttp3.Request; import okhttp3.Response; import okhttp3.mockwebserver.MockResponse; @@ -33,11 +35,13 @@ public class ClientAndServer { MockWebServer server = new MockWebServer(); server.setServerSocketFactory(new UnixDomainServerSocketFactory(socketFile)); + server.setProtocols(Collections.singletonList(Protocol.H2_PRIOR_KNOWLEDGE)); server.enqueue(new MockResponse().setBody("hello")); server.start(); OkHttpClient client = new OkHttpClient.Builder() .socketFactory(new UnixDomainSocketFactory(socketFile)) + .protocols(Collections.singletonList(Protocol.H2_PRIOR_KNOWLEDGE)) .build(); Request request = new Request.Builder() diff --git a/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainServerSocketFactory.java b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainServerSocketFactory.java index f9ec4a08f892eec56e07a212dd722d8f60d89178..c1d63b5a321d837067d5bab07c0e32428e48aedf 100644 --- a/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainServerSocketFactory.java +++ b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainServerSocketFactory.java @@ -24,7 +24,6 @@ import java.net.Socket; import java.net.SocketAddress; import javax.net.ServerSocketFactory; import jnr.unixsocket.UnixServerSocketChannel; -import jnr.unixsocket.UnixSocket; import jnr.unixsocket.UnixSocketAddress; import jnr.unixsocket.UnixSocketChannel; @@ -82,13 +81,8 @@ public final class UnixDomainServerSocketFactory extends ServerSocketFactory { } @Override public Socket accept() throws IOException { - UnixSocketChannel socketChannel = serverSocketChannel.accept(); - - return new UnixSocket(socketChannel) { - @Override public InetAddress getInetAddress() { - return endpoint.getAddress(); // TODO(jwilson): fake the remote address? - } - }; + UnixSocketChannel channel = serverSocketChannel.accept(); + return new BlockingUnixSocket(path, channel, endpoint); } @Override public void close() throws IOException { diff --git a/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainSocketFactory.java b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainSocketFactory.java index 8b16e398f7f7ce6f382a779b86fcd37e8a80926b..e288dc5287c0c995c5dee0537f395ea5705a578b 100644 --- a/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainSocketFactory.java +++ b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainSocketFactory.java @@ -18,12 +18,8 @@ package okhttp3.unixdomainsockets; import java.io.File; import java.io.IOException; import java.net.InetAddress; -import java.net.InetSocketAddress; import java.net.Socket; -import java.net.SocketAddress; import javax.net.SocketFactory; -import jnr.unixsocket.UnixSocket; -import jnr.unixsocket.UnixSocketAddress; import jnr.unixsocket.UnixSocketChannel; /** Impersonate TCP-style SocketFactory over UNIX domain sockets. */ @@ -36,27 +32,7 @@ public final class UnixDomainSocketFactory extends SocketFactory { private Socket createUnixDomainSocket() throws IOException { UnixSocketChannel channel = UnixSocketChannel.open(); - - return new UnixSocket(channel) { - private InetSocketAddress inetSocketAddress; - - @Override public void connect(SocketAddress endpoint) throws IOException { - connect(endpoint, Integer.valueOf(0)); - } - - @Override public void connect(SocketAddress endpoint, int timeout) throws IOException { - connect(endpoint, Integer.valueOf(timeout)); - } - - @Override public void connect(SocketAddress endpoint, Integer timeout) throws IOException { - this.inetSocketAddress = (InetSocketAddress) endpoint; - super.connect(new UnixSocketAddress(path), timeout); - } - - @Override public InetAddress getInetAddress() { - return inetSocketAddress.getAddress(); // TODO(jwilson): fake the remote address? - } - }; + return new BlockingUnixSocket(path, channel); } @Override public Socket createSocket() throws IOException {