MockWebServer.java revision a0de05e00f3d51df7461cb2048c50bb30ef9112a
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. 56b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 57b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonpublic final class MockWebServer { 58b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 59b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson static final String ASCII = "US-ASCII"; 60b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 61b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson private static final Logger logger = Logger.getLogger(MockWebServer.class.getName()); 62b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private final BlockingQueue<RecordedRequest> requestQueue 63b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson = new LinkedBlockingQueue<RecordedRequest>(); 64b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private final BlockingQueue<MockResponse> responseQueue 65b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson = new LinkedBlockingDeque<MockResponse>(); 66096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson private final Set<Socket> openClientSockets 67bc4c79c6a2059003f695f7ad204de36700e8d701Jesse Wilson = Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>()); 68096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson private boolean singleResponse; 6951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson private final AtomicInteger requestCount = new AtomicInteger(); 70b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private int bodyLimit = Integer.MAX_VALUE; 7151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson private ServerSocket serverSocket; 72706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson private SSLSocketFactory sslSocketFactory; 7351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson private ExecutorService executor; 74706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson private boolean tunnelProxy; 75b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 76b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private int port = -1; 77b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 78b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson public int getPort() { 79b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (port == -1) { 80b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson throw new IllegalStateException("Cannot retrieve port before calling play()"); 81b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 82b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return port; 83b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 84b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 850c2fd828abec671333b8b88281825fd27a783723Jesse Wilson public String getHostName() { 860c2fd828abec671333b8b88281825fd27a783723Jesse Wilson return InetAddress.getLoopbackAddress().getHostName(); 870c2fd828abec671333b8b88281825fd27a783723Jesse Wilson } 880c2fd828abec671333b8b88281825fd27a783723Jesse Wilson 8960476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson public Proxy toProxyAddress() { 900c2fd828abec671333b8b88281825fd27a783723Jesse Wilson return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getHostName(), getPort())); 9160476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson } 9260476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson 93b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 94b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Returns a URL for connecting to this server. 95b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * 96b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * @param path the request path, such as "/". 97b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 9800feece22909b7dc79fc96d666d157390b93858eJesse Wilson public URL getUrl(String path) throws MalformedURLException, UnknownHostException { 99096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson return sslSocketFactory != null 1000c2fd828abec671333b8b88281825fd27a783723Jesse Wilson ? new URL("https://" + getHostName() + ":" + getPort() + path) 1010c2fd828abec671333b8b88281825fd27a783723Jesse Wilson : new URL("http://" + getHostName() + ":" + getPort() + path); 102b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 103b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 104b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 105b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Sets the number of bytes of the POST body to keep in memory to the given 106b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * limit. 107b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 108b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson public void setBodyLimit(int maxBodyLength) { 109b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson this.bodyLimit = maxBodyLength; 110b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 111b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 112706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson /** 113706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * Serve requests with HTTPS rather than otherwise. 114706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * 115706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * @param tunnelProxy whether to expect the HTTP CONNECT method before 116706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * negotiating TLS. 117706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson */ 118706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) { 119706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson this.sslSocketFactory = sslSocketFactory; 120706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson this.tunnelProxy = tunnelProxy; 121b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 122b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 123b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 124b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Awaits the next HTTP request, removes it, and returns it. Callers should 125b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * use this to verify the request sent was as intended. 126b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 127b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson public RecordedRequest takeRequest() throws InterruptedException { 128b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return requestQueue.take(); 129b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 130b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 13151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson /** 13251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson * Returns the number of HTTP requests received thus far by this server. 13351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson * This may exceed the number of HTTP connections when connection reuse is 13451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson * in practice. 13551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson */ 13651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson public int getRequestCount() { 13751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson return requestCount.get(); 13851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson } 13951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson 140706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson public void enqueue(MockResponse response) { 1410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson responseQueue.add(response.clone()); 142706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson } 143706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson 144b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 145096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * By default, this class processes requests coming in by adding them to a 146096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * queue and serves responses by removing them from another queue. This mode 147096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * is appropriate for correctness testing. 148096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * 149096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * <p>Serving a single response causes the server to be stateless: requests 150096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * are not enqueued, and responses are not dequeued. This mode is appropriate 151096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * for benchmarking. 152096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson */ 153096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson public void setSingleResponse(boolean singleResponse) { 154096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson this.singleResponse = singleResponse; 155096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 156096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson 157096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson /** 1580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * Equivalent to {@code play(0)}. 1590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson */ 1600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson public void play() throws IOException { 1610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson play(0); 1620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson } 1630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson 1640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson /** 165b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Starts the server, serves all enqueued requests, and shuts the server 166b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * down. 1670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * 1680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * @param port the port to listen to, or 0 for any available port. 1690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * Automated tests should always use port 0 to avoid flakiness when a 1700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * specific port is unavailable. 171b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 1720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson public void play(int port) throws IOException { 17351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson executor = Executors.newCachedThreadPool(); 1740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson serverSocket = new ServerSocket(port); 17551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson serverSocket.setReuseAddress(true); 176706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson 1770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson this.port = serverSocket.getLocalPort(); 178b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() { 179b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson public void run() { 18051e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson try { 181096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson acceptConnections(); 182096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } catch (Throwable e) { 183b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.log(Level.WARNING, "MockWebServer connection failed", e); 184096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 185b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 186096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson /* 187096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * This gnarly block of code will release all sockets and 188096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * all thread, even if any close fails. 189096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson */ 190096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson try { 191096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson serverSocket.close(); 192096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } catch (Throwable e) { 193b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.log(Level.WARNING, "MockWebServer server socket close failed", e); 194096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 195b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext();) { 196096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson try { 197096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson s.next().close(); 198096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson s.remove(); 199096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } catch (Throwable e) { 200b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.log(Level.WARNING, "MockWebServer socket close failed", e); 20151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson } 202096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 203096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson try { 204096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson executor.shutdown(); 205096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } catch (Throwable e) { 206b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.log(Level.WARNING, "MockWebServer executor shutdown failed", e); 207b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 208b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 209096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson 210b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson private void acceptConnections() throws Exception { 211f162edaa335461474b020027bb2e85eb3be2c179Jesse Wilson do { 212b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson Socket socket; 213b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson try { 214b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson socket = serverSocket.accept(); 215b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } catch (SocketException ignored) { 216b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson continue; 217096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 218b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson MockResponse peek = responseQueue.peek(); 219b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson if (peek != null && peek.getSocketPolicy() == DISCONNECT_AT_START) { 2204559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom responseQueue.take(); 2214559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom socket.close(); 222b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } else { 223b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson openClientSockets.add(socket); 224b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson serveConnection(socket); 2254559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom } 226f162edaa335461474b020027bb2e85eb3be2c179Jesse Wilson } while (!responseQueue.isEmpty()); 227096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 228096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson })); 229b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 230b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 23151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson public void shutdown() throws IOException { 23251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson if (serverSocket != null) { 233096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson serverSocket.close(); // should cause acceptConnections() to break out 23451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson } 23551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson } 23651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson 237706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson private void serveConnection(final Socket raw) { 238096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson String name = "MockWebServer-" + raw.getRemoteSocketAddress(); 239b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson executor.execute(namedRunnable(name, new Runnable() { 240706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson int sequenceNumber = 0; 241b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 242b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson public void run() { 243b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson try { 244b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson processConnection(); 245b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } catch (Exception e) { 246b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.log(Level.WARNING, "MockWebServer connection failed", e); 247b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } 248b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } 249b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson 250b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson public void processConnection() throws Exception { 251706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson Socket socket; 252706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson if (sslSocketFactory != null) { 253706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson if (tunnelProxy) { 254c996149b500fc4825156106554457fe2394ae087Jesse Wilson createTunnel(); 255b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 256706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson socket = sslSocketFactory.createSocket( 257706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true); 258706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson ((SSLSocket) socket).setUseClientMode(false); 2598ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson openClientSockets.add(socket); 2608ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson openClientSockets.remove(raw); 261706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson } else { 262706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson socket = raw; 263b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 264b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 265706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson InputStream in = new BufferedInputStream(socket.getInputStream()); 266706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson OutputStream out = new BufferedOutputStream(socket.getOutputStream()); 267706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson 268b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson while (!responseQueue.isEmpty() && processOneRequest(in, out, socket)) {} 269b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson 270b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson if (sequenceNumber == 0) { 271b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.warning("MockWebServer connection didn't make a request"); 272706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson } 273706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson 274b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson in.close(); 275b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.close(); 2768ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson socket.close(); 277b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson if (responseQueue.isEmpty()) { 278b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson shutdown(); 279b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } 2808ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson openClientSockets.remove(socket); 281b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 282706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson 283706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson /** 284c996149b500fc4825156106554457fe2394ae087Jesse Wilson * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response 285c996149b500fc4825156106554457fe2394ae087Jesse Wilson * is dispatched. 286c996149b500fc4825156106554457fe2394ae087Jesse Wilson */ 287c996149b500fc4825156106554457fe2394ae087Jesse Wilson private void createTunnel() throws IOException, InterruptedException { 288c996149b500fc4825156106554457fe2394ae087Jesse Wilson while (true) { 289c996149b500fc4825156106554457fe2394ae087Jesse Wilson MockResponse connect = responseQueue.peek(); 290c996149b500fc4825156106554457fe2394ae087Jesse Wilson if (!processOneRequest(raw.getInputStream(), raw.getOutputStream(), raw)) { 291c996149b500fc4825156106554457fe2394ae087Jesse Wilson throw new IllegalStateException("Tunnel without any CONNECT!"); 292c996149b500fc4825156106554457fe2394ae087Jesse Wilson } 293c996149b500fc4825156106554457fe2394ae087Jesse Wilson if (connect.getSocketPolicy() == SocketPolicy.UPGRADE_TO_SSL_AT_END) { 294c996149b500fc4825156106554457fe2394ae087Jesse Wilson return; 295c996149b500fc4825156106554457fe2394ae087Jesse Wilson } 296c996149b500fc4825156106554457fe2394ae087Jesse Wilson } 297c996149b500fc4825156106554457fe2394ae087Jesse Wilson } 298c996149b500fc4825156106554457fe2394ae087Jesse Wilson 299c996149b500fc4825156106554457fe2394ae087Jesse Wilson /** 300706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * Reads a request and writes its response. Returns true if a request 301706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * was processed. 302706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson */ 303e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson private boolean processOneRequest(InputStream in, OutputStream out, Socket socket) 304706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson throws IOException, InterruptedException { 305706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson RecordedRequest request = readRequest(in, sequenceNumber); 306706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson if (request == null) { 307706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson return false; 308706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson } 309096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson MockResponse response = dispatch(request); 31051e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson writeResponse(out, response); 311e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) { 31251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson in.close(); 31351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson out.close(); 314e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) { 315e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson socket.shutdownInput(); 316e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) { 317e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson socket.shutdownOutput(); 31851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson } 319706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson sequenceNumber++; 320706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson return true; 321706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson } 322096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson })); 323b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 324b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 325b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 326b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * @param sequenceNumber the index of this request on this connection. 327b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 328b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException { 329b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson String request; 330b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson try { 331b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson request = readAsciiUntilCrlf(in); 332b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } catch (IOException streamIsClosed) { 333b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson return null; // no request because we closed the stream 334b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } 335b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (request.isEmpty()) { 336b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson return null; // no request because the stream is exhausted 337b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 338b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 339b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson List<String> headers = new ArrayList<String>(); 340b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson int contentLength = -1; 341b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson boolean chunked = false; 342b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson String header; 343b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson while (!(header = readAsciiUntilCrlf(in)).isEmpty()) { 344b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson headers.add(header); 345b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson String lowercaseHeader = header.toLowerCase(); 346b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) { 347b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson contentLength = Integer.parseInt(header.substring(15).trim()); 348b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 349b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (lowercaseHeader.startsWith("transfer-encoding:") && 350b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson lowercaseHeader.substring(18).trim().equals("chunked")) { 351b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson chunked = true; 352b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 353b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 354b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 355fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson boolean hasBody = false; 356b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson TruncatingOutputStream requestBody = new TruncatingOutputStream(); 357b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson List<Integer> chunkSizes = new ArrayList<Integer>(); 358b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (contentLength != -1) { 359fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson hasBody = true; 360b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson transfer(contentLength, in, requestBody); 361b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } else if (chunked) { 362fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson hasBody = true; 363b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson while (true) { 364b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16); 365b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (chunkSize == 0) { 366b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson readEmptyLine(in); 367b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson break; 368b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 369b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson chunkSizes.add(chunkSize); 370b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson transfer(chunkSize, in, requestBody); 371b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson readEmptyLine(in); 372b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 373b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 374b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 3750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson if (request.startsWith("OPTIONS ") || request.startsWith("GET ") 3760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson || request.startsWith("HEAD ") || request.startsWith("DELETE ") 377adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson || request .startsWith("TRACE ") || request.startsWith("CONNECT ")) { 378fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson if (hasBody) { 3790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson throw new IllegalArgumentException("Request must not have a body: " + request); 380fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson } 3810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson } else if (request.startsWith("POST ") || request.startsWith("PUT ")) { 382fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson if (!hasBody) { 3830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson throw new IllegalArgumentException("Request must have a body: " + request); 384fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson } 385fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson } else { 386fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson throw new UnsupportedOperationException("Unexpected method: " + request); 387fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson } 388fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson 389b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return new RecordedRequest(request, headers, chunkSizes, 390b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber); 391b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 392b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 393b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 394b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Returns a response to satisfy {@code request}. 395b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 396096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson private MockResponse dispatch(RecordedRequest request) throws InterruptedException { 397211d3bbada505912bb16e9d1a6c1a9f1f5c16cffJesse Wilson if (responseQueue.isEmpty()) { 398b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson throw new IllegalStateException("Unexpected request: " + request); 399b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 400096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson 4010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson // to permit interactive/browser testing, ignore requests for favicons 4020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson if (request.getRequestLine().equals("GET /favicon.ico HTTP/1.1")) { 4030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson System.out.println("served " + request.getRequestLine()); 4040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson return new MockResponse() 4050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND); 4060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson } 4070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson 408096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson if (singleResponse) { 409096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson return responseQueue.peek(); 410096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } else { 411096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson requestCount.incrementAndGet(); 412096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson requestQueue.add(request); 413096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson return responseQueue.take(); 414096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 415b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 416b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 417b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private void writeResponse(OutputStream out, MockResponse response) throws IOException { 418b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.write((response.getStatus() + "\r\n").getBytes(ASCII)); 419b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson for (String header : response.getHeaders()) { 420b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.write((header + "\r\n").getBytes(ASCII)); 421b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 422b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.write(("\r\n").getBytes(ASCII)); 423b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.write(response.getBody()); 424b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.flush(); 425b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 426b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 427b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 428b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Transfer bytes from {@code in} to {@code out} until either {@code length} 429b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * bytes have been transferred or {@code in} is exhausted. 430b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 431b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private void transfer(int length, InputStream in, OutputStream out) throws IOException { 432b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson byte[] buffer = new byte[1024]; 433b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson while (length > 0) { 434b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson int count = in.read(buffer, 0, Math.min(buffer.length, length)); 435b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (count == -1) { 436b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return; 437b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 438b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.write(buffer, 0, count); 439b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson length -= count; 440b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 441b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 442b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 443b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 444b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Returns the text from {@code in} until the next "\r\n", or null if 445b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * {@code in} is exhausted. 446b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 447b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private String readAsciiUntilCrlf(InputStream in) throws IOException { 448b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson StringBuilder builder = new StringBuilder(); 449b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson while (true) { 450b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson int c = in.read(); 451b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') { 452b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson builder.deleteCharAt(builder.length() - 1); 453b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return builder.toString(); 454b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } else if (c == -1) { 455b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return builder.toString(); 456b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } else { 457b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson builder.append((char) c); 458b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 459b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 460b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 461b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 462b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private void readEmptyLine(InputStream in) throws IOException { 463b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson String line = readAsciiUntilCrlf(in); 464b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (!line.isEmpty()) { 465b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson throw new IllegalStateException("Expected empty but was: " + line); 466b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 467b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 468b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 469b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 470b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * An output stream that drops data after bodyLimit bytes. 471b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 472b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private class TruncatingOutputStream extends ByteArrayOutputStream { 473b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private int numBytesReceived = 0; 474b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson @Override public void write(byte[] buffer, int offset, int len) { 475b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson numBytesReceived += len; 476b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson super.write(buffer, offset, Math.min(len, bodyLimit - count)); 477b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 478b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson @Override public void write(int oneByte) { 479b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson numBytesReceived++; 480b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (count < bodyLimit) { 481b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson super.write(oneByte); 482b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 483b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 484b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 485096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson 486b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson private static Runnable namedRunnable(final String name, final Runnable runnable) { 487b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson return new Runnable() { 488b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson public void run() { 489096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson String originalName = Thread.currentThread().getName(); 490096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson Thread.currentThread().setName(name); 491096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson try { 492b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson runnable.run(); 493096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } finally { 494096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson Thread.currentThread().setName(originalName); 495096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 496096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 497096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson }; 498096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 499fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson} 500