1b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson/* 2b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Copyright (C) 2010 The Android Open Source Project 3b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * 4b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Licensed under the Apache License, Version 2.0 (the "License"); 5b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * you may not use this file except in compliance with the License. 6b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * You may obtain a copy of the License at 7b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * 8b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * http://www.apache.org/licenses/LICENSE-2.0 9b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * 10b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Unless required by applicable law or agreed to in writing, software 11b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * distributed under the License is distributed on an "AS IS" BASIS, 12b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * See the License for the specific language governing permissions and 14b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * limitations under the License. 15b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 16b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 17b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonpackage tests.http; 18b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 19b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.io.BufferedInputStream; 20b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.io.BufferedOutputStream; 21b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.io.ByteArrayOutputStream; 22b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.io.IOException; 23b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.io.InputStream; 24b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.io.OutputStream; 250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.net.HttpURLConnection; 26a0de05e00f3d51df7461cb2048c50bb30ef9112aJesse Wilsonimport java.net.InetAddress; 2760476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilsonimport java.net.InetSocketAddress; 28b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.net.MalformedURLException; 2960476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilsonimport java.net.Proxy; 30b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.net.ServerSocket; 31b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.net.Socket; 32b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilsonimport java.net.SocketException; 33b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.net.URL; 3400feece22909b7dc79fc96d666d157390b93858eJesse Wilsonimport java.net.UnknownHostException; 35b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.ArrayList; 36096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilsonimport java.util.Collections; 37096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilsonimport java.util.Iterator; 38b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.List; 39096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilsonimport java.util.Set; 40b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.BlockingQueue; 41bc4c79c6a2059003f695f7ad204de36700e8d701Jesse Wilsonimport java.util.concurrent.ConcurrentHashMap; 42b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.ExecutorService; 43b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.Executors; 44b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.LinkedBlockingDeque; 45b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.LinkedBlockingQueue; 4651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilsonimport java.util.concurrent.atomic.AtomicInteger; 47b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilsonimport java.util.logging.Level; 48b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilsonimport java.util.logging.Logger; 49706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilsonimport javax.net.ssl.SSLSocket; 50706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilsonimport javax.net.ssl.SSLSocketFactory; 51e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilsonimport static tests.http.SocketPolicy.DISCONNECT_AT_START; 52b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 53b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson/** 54b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * A scriptable web server. Callers supply canned responses and the server 55b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * replays them upon request in sequence. 5609336c914b4fc813e493acc82469b9ad89fd8694Jesse Wilson * 5709336c914b4fc813e493acc82469b9ad89fd8694Jesse Wilson * @deprecated prefer com.google.mockwebserver.MockWebServer 58b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 5909336c914b4fc813e493acc82469b9ad89fd8694Jesse Wilson@Deprecated 60b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonpublic final class MockWebServer { 61b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 62b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson static final String ASCII = "US-ASCII"; 63b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 64b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson private static final Logger logger = Logger.getLogger(MockWebServer.class.getName()); 65b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private final BlockingQueue<RecordedRequest> requestQueue 66b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson = new LinkedBlockingQueue<RecordedRequest>(); 67b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private final BlockingQueue<MockResponse> responseQueue 68b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson = new LinkedBlockingDeque<MockResponse>(); 69096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson private final Set<Socket> openClientSockets 70bc4c79c6a2059003f695f7ad204de36700e8d701Jesse Wilson = Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>()); 71096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson private boolean singleResponse; 7251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson private final AtomicInteger requestCount = new AtomicInteger(); 73b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private int bodyLimit = Integer.MAX_VALUE; 7451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson private ServerSocket serverSocket; 75706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson private SSLSocketFactory sslSocketFactory; 7651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson private ExecutorService executor; 77706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson private boolean tunnelProxy; 78b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 79b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private int port = -1; 80b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 81b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson public int getPort() { 82b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (port == -1) { 83b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson throw new IllegalStateException("Cannot retrieve port before calling play()"); 84b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 85b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return port; 86b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 87b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 880c2fd828abec671333b8b88281825fd27a783723Jesse Wilson public String getHostName() { 890c2fd828abec671333b8b88281825fd27a783723Jesse Wilson return InetAddress.getLoopbackAddress().getHostName(); 900c2fd828abec671333b8b88281825fd27a783723Jesse Wilson } 910c2fd828abec671333b8b88281825fd27a783723Jesse Wilson 9260476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson public Proxy toProxyAddress() { 930c2fd828abec671333b8b88281825fd27a783723Jesse Wilson return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getHostName(), getPort())); 9460476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson } 9560476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson 96b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 97b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Returns a URL for connecting to this server. 98b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * 99b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * @param path the request path, such as "/". 100b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 10100feece22909b7dc79fc96d666d157390b93858eJesse Wilson public URL getUrl(String path) throws MalformedURLException, UnknownHostException { 102096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson return sslSocketFactory != null 1030c2fd828abec671333b8b88281825fd27a783723Jesse Wilson ? new URL("https://" + getHostName() + ":" + getPort() + path) 1040c2fd828abec671333b8b88281825fd27a783723Jesse Wilson : new URL("http://" + getHostName() + ":" + getPort() + path); 105b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 106b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 107b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 108b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Sets the number of bytes of the POST body to keep in memory to the given 109b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * limit. 110b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 111b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson public void setBodyLimit(int maxBodyLength) { 112b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson this.bodyLimit = maxBodyLength; 113b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 114b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 115706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson /** 116706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * Serve requests with HTTPS rather than otherwise. 117706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * 118706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * @param tunnelProxy whether to expect the HTTP CONNECT method before 119706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * negotiating TLS. 120706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson */ 121706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) { 122706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson this.sslSocketFactory = sslSocketFactory; 123706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson this.tunnelProxy = tunnelProxy; 124b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 125b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 126b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 127b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Awaits the next HTTP request, removes it, and returns it. Callers should 128b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * use this to verify the request sent was as intended. 129b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 130b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson public RecordedRequest takeRequest() throws InterruptedException { 131b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return requestQueue.take(); 132b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 133b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 13451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson /** 13551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson * Returns the number of HTTP requests received thus far by this server. 13651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson * This may exceed the number of HTTP connections when connection reuse is 13751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson * in practice. 13851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson */ 13951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson public int getRequestCount() { 14051e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson return requestCount.get(); 14151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson } 14251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson 143706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson public void enqueue(MockResponse response) { 1440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson responseQueue.add(response.clone()); 145706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson } 146706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson 147b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 148096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * By default, this class processes requests coming in by adding them to a 149096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * queue and serves responses by removing them from another queue. This mode 150096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * is appropriate for correctness testing. 151096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * 152096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * <p>Serving a single response causes the server to be stateless: requests 153096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * are not enqueued, and responses are not dequeued. This mode is appropriate 154096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * for benchmarking. 155096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson */ 156096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson public void setSingleResponse(boolean singleResponse) { 157096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson this.singleResponse = singleResponse; 158096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 159096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson 160096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson /** 1610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * Equivalent to {@code play(0)}. 1620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson */ 1630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson public void play() throws IOException { 1640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson play(0); 1650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson } 1660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson 1670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson /** 168b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Starts the server, serves all enqueued requests, and shuts the server 169b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * down. 1700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * 1710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * @param port the port to listen to, or 0 for any available port. 1720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * Automated tests should always use port 0 to avoid flakiness when a 1730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * specific port is unavailable. 174b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 1750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson public void play(int port) throws IOException { 17651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson executor = Executors.newCachedThreadPool(); 1770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson serverSocket = new ServerSocket(port); 17851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson serverSocket.setReuseAddress(true); 179706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson 1800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson this.port = serverSocket.getLocalPort(); 181b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() { 182b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson public void run() { 18351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson try { 184096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson acceptConnections(); 185096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } catch (Throwable e) { 186b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.log(Level.WARNING, "MockWebServer connection failed", e); 187096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 188b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 189096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson /* 190096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * This gnarly block of code will release all sockets and 191096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * all thread, even if any close fails. 192096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson */ 193096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson try { 194096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson serverSocket.close(); 195096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } catch (Throwable e) { 196b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.log(Level.WARNING, "MockWebServer server socket close failed", e); 197096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 198b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext();) { 199096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson try { 200096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson s.next().close(); 201096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson s.remove(); 202096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } catch (Throwable e) { 203b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.log(Level.WARNING, "MockWebServer socket close failed", e); 20451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson } 205096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 206096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson try { 207096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson executor.shutdown(); 208096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } catch (Throwable e) { 209b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.log(Level.WARNING, "MockWebServer executor shutdown failed", e); 210b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 211b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 212096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson 213b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson private void acceptConnections() throws Exception { 214f162edaa335461474b020027bb2e85eb3be2c179Jesse Wilson do { 215b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson Socket socket; 216b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson try { 217b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson socket = serverSocket.accept(); 218b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } catch (SocketException ignored) { 219b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson continue; 220096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 221b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson MockResponse peek = responseQueue.peek(); 222b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson if (peek != null && peek.getSocketPolicy() == DISCONNECT_AT_START) { 2234559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom responseQueue.take(); 2244559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom socket.close(); 225b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } else { 226b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson openClientSockets.add(socket); 227b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson serveConnection(socket); 2284559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom } 229f162edaa335461474b020027bb2e85eb3be2c179Jesse Wilson } while (!responseQueue.isEmpty()); 230096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 231096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson })); 232b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 233b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 23451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson public void shutdown() throws IOException { 23551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson if (serverSocket != null) { 236096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson serverSocket.close(); // should cause acceptConnections() to break out 23751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson } 23851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson } 23951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson 240706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson private void serveConnection(final Socket raw) { 241096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson String name = "MockWebServer-" + raw.getRemoteSocketAddress(); 242b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson executor.execute(namedRunnable(name, new Runnable() { 243706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson int sequenceNumber = 0; 244b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 245b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson public void run() { 246b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson try { 247b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson processConnection(); 248b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } catch (Exception e) { 249b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.log(Level.WARNING, "MockWebServer connection failed", e); 250b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } 251b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } 252b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson 253b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson public void processConnection() throws Exception { 254706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson Socket socket; 255706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson if (sslSocketFactory != null) { 256706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson if (tunnelProxy) { 257c996149b500fc4825156106554457fe2394ae087Jesse Wilson createTunnel(); 258b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 259706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson socket = sslSocketFactory.createSocket( 260706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true); 261706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson ((SSLSocket) socket).setUseClientMode(false); 2628ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson openClientSockets.add(socket); 2638ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson openClientSockets.remove(raw); 264706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson } else { 265706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson socket = raw; 266b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 267b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 268706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson InputStream in = new BufferedInputStream(socket.getInputStream()); 269706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson OutputStream out = new BufferedOutputStream(socket.getOutputStream()); 270706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson 271b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson while (!responseQueue.isEmpty() && processOneRequest(in, out, socket)) {} 272b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson 273b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson if (sequenceNumber == 0) { 274b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.warning("MockWebServer connection didn't make a request"); 275706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson } 276706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson 277b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson in.close(); 278b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.close(); 2798ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson socket.close(); 280b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson if (responseQueue.isEmpty()) { 281b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson shutdown(); 282b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } 2838ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson openClientSockets.remove(socket); 284b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 285706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson 286706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson /** 287c996149b500fc4825156106554457fe2394ae087Jesse Wilson * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response 288c996149b500fc4825156106554457fe2394ae087Jesse Wilson * is dispatched. 289c996149b500fc4825156106554457fe2394ae087Jesse Wilson */ 290c996149b500fc4825156106554457fe2394ae087Jesse Wilson private void createTunnel() throws IOException, InterruptedException { 291c996149b500fc4825156106554457fe2394ae087Jesse Wilson while (true) { 292c996149b500fc4825156106554457fe2394ae087Jesse Wilson MockResponse connect = responseQueue.peek(); 293c996149b500fc4825156106554457fe2394ae087Jesse Wilson if (!processOneRequest(raw.getInputStream(), raw.getOutputStream(), raw)) { 294c996149b500fc4825156106554457fe2394ae087Jesse Wilson throw new IllegalStateException("Tunnel without any CONNECT!"); 295c996149b500fc4825156106554457fe2394ae087Jesse Wilson } 296c996149b500fc4825156106554457fe2394ae087Jesse Wilson if (connect.getSocketPolicy() == SocketPolicy.UPGRADE_TO_SSL_AT_END) { 297c996149b500fc4825156106554457fe2394ae087Jesse Wilson return; 298c996149b500fc4825156106554457fe2394ae087Jesse Wilson } 299c996149b500fc4825156106554457fe2394ae087Jesse Wilson } 300c996149b500fc4825156106554457fe2394ae087Jesse Wilson } 301c996149b500fc4825156106554457fe2394ae087Jesse Wilson 302c996149b500fc4825156106554457fe2394ae087Jesse Wilson /** 303706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * Reads a request and writes its response. Returns true if a request 304706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * was processed. 305706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson */ 306e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson private boolean processOneRequest(InputStream in, OutputStream out, Socket socket) 307706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson throws IOException, InterruptedException { 308706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson RecordedRequest request = readRequest(in, sequenceNumber); 309706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson if (request == null) { 310706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson return false; 311706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson } 312096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson MockResponse response = dispatch(request); 31351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson writeResponse(out, response); 314e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) { 31551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson in.close(); 31651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson out.close(); 317e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) { 318e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson socket.shutdownInput(); 319e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) { 320e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson socket.shutdownOutput(); 32151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson } 322706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson sequenceNumber++; 323706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson return true; 324706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson } 325096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson })); 326b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 327b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 328b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 329b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * @param sequenceNumber the index of this request on this connection. 330b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 331b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException { 332b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson String request; 333b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson try { 334b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson request = readAsciiUntilCrlf(in); 335b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } catch (IOException streamIsClosed) { 336b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson return null; // no request because we closed the stream 337b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } 338b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (request.isEmpty()) { 339b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson return null; // no request because the stream is exhausted 340b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 341b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 342b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson List<String> headers = new ArrayList<String>(); 343b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson int contentLength = -1; 344b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson boolean chunked = false; 345b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson String header; 346b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson while (!(header = readAsciiUntilCrlf(in)).isEmpty()) { 347b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson headers.add(header); 348b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson String lowercaseHeader = header.toLowerCase(); 349b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) { 350b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson contentLength = Integer.parseInt(header.substring(15).trim()); 351b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 352b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (lowercaseHeader.startsWith("transfer-encoding:") && 353b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson lowercaseHeader.substring(18).trim().equals("chunked")) { 354b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson chunked = true; 355b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 356b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 357b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 358fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson boolean hasBody = false; 359b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson TruncatingOutputStream requestBody = new TruncatingOutputStream(); 360b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson List<Integer> chunkSizes = new ArrayList<Integer>(); 361b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (contentLength != -1) { 362fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson hasBody = true; 363b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson transfer(contentLength, in, requestBody); 364b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } else if (chunked) { 365fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson hasBody = true; 366b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson while (true) { 367b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16); 368b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (chunkSize == 0) { 369b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson readEmptyLine(in); 370b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson break; 371b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 372b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson chunkSizes.add(chunkSize); 373b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson transfer(chunkSize, in, requestBody); 374b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson readEmptyLine(in); 375b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 376b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 377b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 3780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson if (request.startsWith("OPTIONS ") || request.startsWith("GET ") 3790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson || request.startsWith("HEAD ") || request.startsWith("DELETE ") 380adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson || request .startsWith("TRACE ") || request.startsWith("CONNECT ")) { 381fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson if (hasBody) { 3820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson throw new IllegalArgumentException("Request must not have a body: " + request); 383fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson } 3840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson } else if (request.startsWith("POST ") || request.startsWith("PUT ")) { 385fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson if (!hasBody) { 3860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson throw new IllegalArgumentException("Request must have a body: " + request); 387fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson } 388fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson } else { 389fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson throw new UnsupportedOperationException("Unexpected method: " + request); 390fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson } 391fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson 392b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return new RecordedRequest(request, headers, chunkSizes, 393b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber); 394b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 395b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 396b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 397b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Returns a response to satisfy {@code request}. 398b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 399096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson private MockResponse dispatch(RecordedRequest request) throws InterruptedException { 400211d3bbada505912bb16e9d1a6c1a9f1f5c16cffJesse Wilson if (responseQueue.isEmpty()) { 401b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson throw new IllegalStateException("Unexpected request: " + request); 402b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 403096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson 4040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson // to permit interactive/browser testing, ignore requests for favicons 4050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson if (request.getRequestLine().equals("GET /favicon.ico HTTP/1.1")) { 4060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson System.out.println("served " + request.getRequestLine()); 4070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson return new MockResponse() 4080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND); 4090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson } 4100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson 411096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson if (singleResponse) { 412096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson return responseQueue.peek(); 413096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } else { 414096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson requestCount.incrementAndGet(); 415096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson requestQueue.add(request); 416096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson return responseQueue.take(); 417096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 418b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 419b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 420b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private void writeResponse(OutputStream out, MockResponse response) throws IOException { 421b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.write((response.getStatus() + "\r\n").getBytes(ASCII)); 422b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson for (String header : response.getHeaders()) { 423b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.write((header + "\r\n").getBytes(ASCII)); 424b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 425b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.write(("\r\n").getBytes(ASCII)); 426b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.write(response.getBody()); 427b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.flush(); 428b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 429b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 430b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 431b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Transfer bytes from {@code in} to {@code out} until either {@code length} 432b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * bytes have been transferred or {@code in} is exhausted. 433b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 434b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private void transfer(int length, InputStream in, OutputStream out) throws IOException { 435b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson byte[] buffer = new byte[1024]; 436b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson while (length > 0) { 437b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson int count = in.read(buffer, 0, Math.min(buffer.length, length)); 438b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (count == -1) { 439b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return; 440b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 441b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.write(buffer, 0, count); 442b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson length -= count; 443b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 444b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 445b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 446b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 447b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Returns the text from {@code in} until the next "\r\n", or null if 448b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * {@code in} is exhausted. 449b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 450b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private String readAsciiUntilCrlf(InputStream in) throws IOException { 451b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson StringBuilder builder = new StringBuilder(); 452b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson while (true) { 453b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson int c = in.read(); 454b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') { 455b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson builder.deleteCharAt(builder.length() - 1); 456b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return builder.toString(); 457b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } else if (c == -1) { 458b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return builder.toString(); 459b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } else { 460b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson builder.append((char) c); 461b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 462b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 463b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 464b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 465b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private void readEmptyLine(InputStream in) throws IOException { 466b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson String line = readAsciiUntilCrlf(in); 467b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (!line.isEmpty()) { 468b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson throw new IllegalStateException("Expected empty but was: " + line); 469b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 470b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 471b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 472b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 473b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * An output stream that drops data after bodyLimit bytes. 474b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 475b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private class TruncatingOutputStream extends ByteArrayOutputStream { 476b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private int numBytesReceived = 0; 477b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson @Override public void write(byte[] buffer, int offset, int len) { 478b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson numBytesReceived += len; 479b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson super.write(buffer, offset, Math.min(len, bodyLimit - count)); 480b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 481b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson @Override public void write(int oneByte) { 482b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson numBytesReceived++; 483b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (count < bodyLimit) { 484b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson super.write(oneByte); 485b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 486b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 487b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 488096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson 489b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson private static Runnable namedRunnable(final String name, final Runnable runnable) { 490b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson return new Runnable() { 491b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson public void run() { 492096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson String originalName = Thread.currentThread().getName(); 493096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson Thread.currentThread().setName(name); 494096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson try { 495b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson runnable.run(); 496096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } finally { 497096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson Thread.currentThread().setName(originalName); 498096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 499096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 500096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson }; 501096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 502fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson} 503