diff --git a/okhttp-testing-support/src/main/java/okhttp3/ForwardingRequestBody.java b/okhttp-testing-support/src/main/java/okhttp3/ForwardingRequestBody.java new file mode 100644 index 000000000..4aa144c6b --- /dev/null +++ b/okhttp-testing-support/src/main/java/okhttp3/ForwardingRequestBody.java @@ -0,0 +1,53 @@ +/* + * 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; + +import java.io.IOException; +import javax.annotation.Nullable; +import okio.BufferedSink; + +public class ForwardingRequestBody extends RequestBody { + private final RequestBody delegate; + + public ForwardingRequestBody(RequestBody delegate) { + if (delegate == null) throw new IllegalArgumentException("delegate == null"); + this.delegate = delegate; + } + + public final RequestBody delegate() { + return delegate; + } + + @Override public @Nullable MediaType contentType() { + return delegate.contentType(); + } + + @Override public long contentLength() throws IOException { + return delegate.contentLength(); + } + + @Override public void writeTo(BufferedSink sink) throws IOException { + delegate.writeTo(sink); + } + + @Override public boolean isDuplex() { + return delegate.isDuplex(); + } + + @Override public String toString() { + return getClass().getSimpleName() + "(" + delegate.toString() + ")"; + } +} diff --git a/okhttp-testing-support/src/main/java/okhttp3/ForwardingResponseBody.java b/okhttp-testing-support/src/main/java/okhttp3/ForwardingResponseBody.java new file mode 100644 index 000000000..2cc067ac0 --- /dev/null +++ b/okhttp-testing-support/src/main/java/okhttp3/ForwardingResponseBody.java @@ -0,0 +1,48 @@ +/* + * 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; + +import javax.annotation.Nullable; +import okio.BufferedSource; + +public class ForwardingResponseBody extends ResponseBody { + private final ResponseBody delegate; + + public ForwardingResponseBody(ResponseBody delegate) { + if (delegate == null) throw new IllegalArgumentException("delegate == null"); + this.delegate = delegate; + } + + public final ResponseBody delegate() { + return delegate; + } + + @Override public @Nullable MediaType contentType() { + return delegate.contentType(); + } + + @Override public long contentLength() { + return delegate.contentLength(); + } + + @Override public BufferedSource source() { + return delegate.source(); + } + + @Override public String toString() { + return getClass().getSimpleName() + "(" + delegate.toString() + ")"; + } +} diff --git a/okhttp-testing-support/src/main/java/okhttp3/UppercaseRequestInterceptor.java b/okhttp-testing-support/src/main/java/okhttp3/UppercaseRequestInterceptor.java new file mode 100644 index 000000000..490c26611 --- /dev/null +++ b/okhttp-testing-support/src/main/java/okhttp3/UppercaseRequestInterceptor.java @@ -0,0 +1,52 @@ +/* + * 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; + +import java.io.IOException; +import okio.Buffer; +import okio.BufferedSink; +import okio.ByteString; +import okio.ForwardingSink; +import okio.Okio; +import okio.Sink; + +/** Rewrites the request body sent to the server to be all uppercase. */ +public final class UppercaseRequestInterceptor implements Interceptor { + @Override public Response intercept(Chain chain) throws IOException { + return chain.proceed(uppercaseRequest(chain.request())); + } + + /** Returns a request that transforms {@code request} to be all uppercase. */ + private Request uppercaseRequest(Request request) { + RequestBody uppercaseBody = new ForwardingRequestBody(request.body()) { + @Override public void writeTo(BufferedSink sink) throws IOException { + delegate().writeTo(Okio.buffer(uppercaseSink(sink))); + } + }; + return request.newBuilder() + .method(request.method(), uppercaseBody) + .build(); + } + + private Sink uppercaseSink(Sink sink) { + return new ForwardingSink(sink) { + @Override public void write(Buffer source, long byteCount) throws IOException { + ByteString bytes = source.readByteString(byteCount); + delegate().write(new Buffer().write(bytes.toAsciiUppercase()), byteCount); + } + }; + } +} diff --git a/okhttp-testing-support/src/main/java/okhttp3/UppercaseResponseInterceptor.java b/okhttp-testing-support/src/main/java/okhttp3/UppercaseResponseInterceptor.java new file mode 100644 index 000000000..7fbf8ef47 --- /dev/null +++ b/okhttp-testing-support/src/main/java/okhttp3/UppercaseResponseInterceptor.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; + +import java.io.IOException; +import okio.Buffer; +import okio.BufferedSource; +import okio.ForwardingSource; +import okio.Okio; + +/** Rewrites the response body returned from the server to be all uppercase. */ +public final class UppercaseResponseInterceptor implements Interceptor { + @Override public Response intercept(Chain chain) throws IOException { + return uppercaseResponse(chain.proceed(chain.request())); + } + + private Response uppercaseResponse(Response response) { + ResponseBody uppercaseBody = new ForwardingResponseBody(response.body()) { + @Override public BufferedSource source() { + return Okio.buffer(uppercaseSource(delegate().source())); + } + }; + return response.newBuilder() + .body(uppercaseBody) + .build(); + } + + private ForwardingSource uppercaseSource(BufferedSource source) { + return new ForwardingSource(source) { + @Override public long read(Buffer sink, long byteCount) throws IOException { + Buffer buffer = new Buffer(); + long read = delegate().read(buffer, byteCount); + if (read != -1L) sink.write(buffer.readByteString().toAsciiUppercase()); + return read; + } + }; + } +} diff --git a/okhttp-tests/src/test/java/okhttp3/DuplexTest.java b/okhttp-tests/src/test/java/okhttp3/DuplexTest.java index 6b86c0776..87536f1c7 100644 --- a/okhttp-tests/src/test/java/okhttp3/DuplexTest.java +++ b/okhttp-tests/src/test/java/okhttp3/DuplexTest.java @@ -442,6 +442,42 @@ public final class DuplexTest { mockDuplexResponseBody.awaitSuccess(); } + @Test public void duplexWithRewriteInterceptors() throws Exception { + enableProtocol(Protocol.HTTP_2); + MockDuplexResponseBody mockDuplexResponseBody = enqueueResponseWithBody( + new MockResponse() + .clearHeaders(), + new MockDuplexResponseBody() + .receiveRequest("REQUEST A\n") + .sendResponse("response B\n") + .exhaustRequest() + .exhaustResponse()); + + client = client.newBuilder() + .addInterceptor(new UppercaseRequestInterceptor()) + .addInterceptor(new UppercaseResponseInterceptor()) + .build(); + + 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.close(); + assertNull(responseBody.readUtf8Line()); + } + + mockDuplexResponseBody.awaitSuccess(); + } + private MockDuplexResponseBody enqueueResponseWithBody( MockResponse response, MockDuplexResponseBody body) { MwsDuplexAccess.instance.setBody(response, body); diff --git a/okhttp/src/main/java/okhttp3/RequestBody.java b/okhttp/src/main/java/okhttp3/RequestBody.java index b5a43e5ab..de2c7cb73 100644 --- a/okhttp/src/main/java/okhttp3/RequestBody.java +++ b/okhttp/src/main/java/okhttp3/RequestBody.java @@ -44,7 +44,8 @@ public abstract class RequestBody { /** * A duplex request body is special in how it is transmitted on the network and - * in the API contract between OkHttp and the application. + * in the API contract between OkHttp and the application. This method returns + * false unless it overridden by a subclass. * *

Duplex Transmission

* @@ -54,19 +55,22 @@ public abstract class RequestBody { * *

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. + * implementation of this pattern is gRPC. * - *

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. + *

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. If you cannot ensure that your client and server both support HTTP/2, do not + * use this feature. * *

Duplex APIs

* *

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}. For duplex request bodies, {@link #writeTo} should return quickly, - * possibly by handing off the provided request body to another thread to perform writing. + * RequestBody#writeTo} after that method returns. For duplex requests bodies that condition is + * lifted. Such writes occur on an application-provided thread and may occur concurrently with + * reads of the {@link ResponseBody}. For duplex request bodies, {@link #writeTo} should return + * quickly, possibly by handing off the provided request body to another thread to perform + * writing. */ public boolean isDuplex() { return false;