1/*
2 * Copyright (C) 2014 Square, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.squareup.okhttp.internal.http;
17
18import com.squareup.okhttp.DelegatingServerSocketFactory;
19import com.squareup.okhttp.DelegatingSocketFactory;
20import com.squareup.okhttp.OkHttpClient;
21import com.squareup.okhttp.OkUrlFactory;
22import com.squareup.okhttp.mockwebserver.MockResponse;
23import com.squareup.okhttp.mockwebserver.MockWebServer;
24
25import java.io.IOException;
26import java.io.InputStream;
27import java.io.InterruptedIOException;
28import java.io.OutputStream;
29import java.net.HttpURLConnection;
30import java.net.ServerSocket;
31import java.net.Socket;
32import java.util.concurrent.TimeUnit;
33
34import okio.Buffer;
35import org.junit.Before;
36import org.junit.Test;
37
38import javax.net.ServerSocketFactory;
39import javax.net.SocketFactory;
40
41import static org.junit.Assert.fail;
42
43public final class ThreadInterruptTest {
44
45  // The size of the socket buffers in bytes.
46  private static final int SOCKET_BUFFER_SIZE = 256 * 1024;
47
48  private MockWebServer server;
49  private OkHttpClient client;
50
51  @Before public void setUp() throws Exception {
52    server = new MockWebServer();
53    client = new OkHttpClient();
54
55    // Sockets on some platforms can have large buffers that mean writes do not block when
56    // required. These socket factories explicitly set the buffer sizes on sockets created.
57    server.setServerSocketFactory(
58        new DelegatingServerSocketFactory(ServerSocketFactory.getDefault()) {
59          @Override
60          protected ServerSocket configureServerSocket(ServerSocket serverSocket)
61              throws IOException {
62            serverSocket.setReceiveBufferSize(SOCKET_BUFFER_SIZE);
63            return serverSocket;
64          }
65        });
66    client.setSocketFactory(new DelegatingSocketFactory(SocketFactory.getDefault()) {
67      @Override
68      protected Socket configureSocket(Socket socket) throws IOException {
69        socket.setSendBufferSize(SOCKET_BUFFER_SIZE);
70        socket.setReceiveBufferSize(SOCKET_BUFFER_SIZE);
71        return socket;
72      }
73    });
74  }
75
76  @Test public void interruptWritingRequestBody() throws Exception {
77    int requestBodySize = 2 * 1024 * 1024; // 2 MiB
78
79    server.enqueue(new MockResponse()
80        .throttleBody(64 * 1024, 125, TimeUnit.MILLISECONDS)); // 500 Kbps
81    server.start();
82
83    interruptLater(500);
84
85    HttpURLConnection connection = new OkUrlFactory(client).open(server.getUrl("/"));
86    connection.setDoOutput(true);
87    connection.setFixedLengthStreamingMode(requestBodySize);
88    OutputStream requestBody = connection.getOutputStream();
89    byte[] buffer = new byte[1024];
90    try {
91      for (int i = 0; i < requestBodySize; i += buffer.length) {
92        requestBody.write(buffer);
93        requestBody.flush();
94      }
95      fail("Expected thread to be interrupted");
96    } catch (InterruptedIOException expected) {
97    }
98
99    connection.disconnect();
100  }
101
102  @Test public void interruptReadingResponseBody() throws Exception {
103    int responseBodySize = 2 * 1024 * 1024; // 2 MiB
104
105    server.enqueue(new MockResponse()
106        .setBody(new Buffer().write(new byte[responseBodySize]))
107        .throttleBody(64 * 1024, 125, TimeUnit.MILLISECONDS)); // 500 Kbps
108    server.start();
109
110    interruptLater(500);
111
112    HttpURLConnection connection = new OkUrlFactory(client).open(server.getUrl("/"));
113    InputStream responseBody = connection.getInputStream();
114    byte[] buffer = new byte[1024];
115    try {
116      while (responseBody.read(buffer) != -1) {
117      }
118      fail("Expected thread to be interrupted");
119    } catch (InterruptedIOException expected) {
120    }
121
122    connection.disconnect();
123  }
124
125  private void interruptLater(final int delayMillis) {
126    final Thread toInterrupt = Thread.currentThread();
127    Thread interruptingCow = new Thread() {
128      @Override public void run() {
129        try {
130          sleep(delayMillis);
131          toInterrupt.interrupt();
132        } catch (InterruptedException e) {
133          throw new RuntimeException(e);
134        }
135      }
136    };
137    interruptingCow.start();
138  }
139}
140