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; 39b274574b7e2681f4411852af9857b4540bf75841Elliott Hughesimport java.util.Locale; 40096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilsonimport java.util.Set; 41b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.BlockingQueue; 42bc4c79c6a2059003f695f7ad204de36700e8d701Jesse Wilsonimport java.util.concurrent.ConcurrentHashMap; 43b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.ExecutorService; 44b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.Executors; 45b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.LinkedBlockingDeque; 46b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.LinkedBlockingQueue; 4751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilsonimport java.util.concurrent.atomic.AtomicInteger; 48b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilsonimport java.util.logging.Level; 49b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilsonimport java.util.logging.Logger; 50706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilsonimport javax.net.ssl.SSLSocket; 51706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilsonimport javax.net.ssl.SSLSocketFactory; 52e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilsonimport static tests.http.SocketPolicy.DISCONNECT_AT_START; 53b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 54b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson/** 55b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * A scriptable web server. Callers supply canned responses and the server 56b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * replays them upon request in sequence. 5709336c914b4fc813e493acc82469b9ad89fd8694Jesse Wilson * 5899b4489d0555c6e0e5df941cbfad4cf250c8f0b8Elliott Hughes * @deprecated Use {@code com.google.mockwebserver.MockWebServer} instead. 59b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 6009336c914b4fc813e493acc82469b9ad89fd8694Jesse Wilson@Deprecated 61b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonpublic final class MockWebServer { 62b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 63b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson static final String ASCII = "US-ASCII"; 64b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 65b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson private static final Logger logger = Logger.getLogger(MockWebServer.class.getName()); 66b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private final BlockingQueue<RecordedRequest> requestQueue 67b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson = new LinkedBlockingQueue<RecordedRequest>(); 68b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private final BlockingQueue<MockResponse> responseQueue 69b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson = new LinkedBlockingDeque<MockResponse>(); 70096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson private final Set<Socket> openClientSockets 71bc4c79c6a2059003f695f7ad204de36700e8d701Jesse Wilson = Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>()); 72096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson private boolean singleResponse; 7351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson private final AtomicInteger requestCount = new AtomicInteger(); 74b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private int bodyLimit = Integer.MAX_VALUE; 7551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson private ServerSocket serverSocket; 76706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson private SSLSocketFactory sslSocketFactory; 7751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson private ExecutorService executor; 78706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson private boolean tunnelProxy; 79b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 80b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private int port = -1; 81b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 82b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson public int getPort() { 83b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (port == -1) { 84b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson throw new IllegalStateException("Cannot retrieve port before calling play()"); 85b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 86b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return port; 87b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 88b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 890c2fd828abec671333b8b88281825fd27a783723Jesse Wilson public String getHostName() { 900c2fd828abec671333b8b88281825fd27a783723Jesse Wilson return InetAddress.getLoopbackAddress().getHostName(); 910c2fd828abec671333b8b88281825fd27a783723Jesse Wilson } 920c2fd828abec671333b8b88281825fd27a783723Jesse Wilson 9360476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson public Proxy toProxyAddress() { 940c2fd828abec671333b8b88281825fd27a783723Jesse Wilson return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getHostName(), getPort())); 9560476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson } 9660476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson 97b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 98b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Returns a URL for connecting to this server. 99b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * 100b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * @param path the request path, such as "/". 101b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 10200feece22909b7dc79fc96d666d157390b93858eJesse Wilson public URL getUrl(String path) throws MalformedURLException, UnknownHostException { 103096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson return sslSocketFactory != null 1040c2fd828abec671333b8b88281825fd27a783723Jesse Wilson ? new URL("https://" + getHostName() + ":" + getPort() + path) 1050c2fd828abec671333b8b88281825fd27a783723Jesse Wilson : new URL("http://" + getHostName() + ":" + getPort() + path); 106b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 107b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 108b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 109b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Sets the number of bytes of the POST body to keep in memory to the given 110b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * limit. 111b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 112b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson public void setBodyLimit(int maxBodyLength) { 113b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson this.bodyLimit = maxBodyLength; 114b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 115b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 116706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson /** 117706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * Serve requests with HTTPS rather than otherwise. 118706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * 119706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * @param tunnelProxy whether to expect the HTTP CONNECT method before 120706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * negotiating TLS. 121706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson */ 122706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) { 123706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson this.sslSocketFactory = sslSocketFactory; 124706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson this.tunnelProxy = tunnelProxy; 125b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 126b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 127b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 128b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Awaits the next HTTP request, removes it, and returns it. Callers should 129b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * use this to verify the request sent was as intended. 130b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 131b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson public RecordedRequest takeRequest() throws InterruptedException { 132b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return requestQueue.take(); 133b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 134b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 13551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson /** 13651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson * Returns the number of HTTP requests received thus far by this server. 13751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson * This may exceed the number of HTTP connections when connection reuse is 13851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson * in practice. 13951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson */ 14051e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson public int getRequestCount() { 14151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson return requestCount.get(); 14251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson } 14351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson 144706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson public void enqueue(MockResponse response) { 1450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson responseQueue.add(response.clone()); 146706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson } 147706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson 148b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 149096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * By default, this class processes requests coming in by adding them to a 150096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * queue and serves responses by removing them from another queue. This mode 151096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * is appropriate for correctness testing. 152096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * 153096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * <p>Serving a single response causes the server to be stateless: requests 154096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * are not enqueued, and responses are not dequeued. This mode is appropriate 155096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * for benchmarking. 156096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson */ 157096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson public void setSingleResponse(boolean singleResponse) { 158096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson this.singleResponse = singleResponse; 159096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 160096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson 161096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson /** 1620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * Equivalent to {@code play(0)}. 1630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson */ 1640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson public void play() throws IOException { 1650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson play(0); 1660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson } 1670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson 1680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson /** 169b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Starts the server, serves all enqueued requests, and shuts the server 170b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * down. 1710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * 1720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * @param port the port to listen to, or 0 for any available port. 1730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * Automated tests should always use port 0 to avoid flakiness when a 1740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * specific port is unavailable. 175b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 1760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson public void play(int port) throws IOException { 17751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson executor = Executors.newCachedThreadPool(); 1780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson serverSocket = new ServerSocket(port); 17951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson serverSocket.setReuseAddress(true); 180706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson 1810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson this.port = serverSocket.getLocalPort(); 182b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() { 183b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson public void run() { 18451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson try { 185096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson acceptConnections(); 186096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } catch (Throwable e) { 187b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.log(Level.WARNING, "MockWebServer connection failed", e); 188096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 189b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 190096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson /* 191096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * This gnarly block of code will release all sockets and 192096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson * all thread, even if any close fails. 193096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson */ 194096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson try { 195096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson serverSocket.close(); 196096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } catch (Throwable e) { 197b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.log(Level.WARNING, "MockWebServer server socket close failed", e); 198096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 199b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext();) { 200096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson try { 201096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson s.next().close(); 202096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson s.remove(); 203096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } catch (Throwable e) { 204b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.log(Level.WARNING, "MockWebServer socket close failed", e); 20551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson } 206096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 207096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson try { 208096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson executor.shutdown(); 209096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } catch (Throwable e) { 210b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.log(Level.WARNING, "MockWebServer executor shutdown failed", e); 211b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 212b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 213096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson 214b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson private void acceptConnections() throws Exception { 215f162edaa335461474b020027bb2e85eb3be2c179Jesse Wilson do { 216b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson Socket socket; 217b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson try { 218b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson socket = serverSocket.accept(); 219b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } catch (SocketException ignored) { 220b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson continue; 221096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 222b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson MockResponse peek = responseQueue.peek(); 223b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson if (peek != null && peek.getSocketPolicy() == DISCONNECT_AT_START) { 2244559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom responseQueue.take(); 2254559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom socket.close(); 226b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } else { 227b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson openClientSockets.add(socket); 228b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson serveConnection(socket); 2294559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom } 230f162edaa335461474b020027bb2e85eb3be2c179Jesse Wilson } while (!responseQueue.isEmpty()); 231096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 232096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson })); 233b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 234b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 23551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson public void shutdown() throws IOException { 23651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson if (serverSocket != null) { 237096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson serverSocket.close(); // should cause acceptConnections() to break out 23851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson } 23951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson } 24051e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson 241706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson private void serveConnection(final Socket raw) { 242096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson String name = "MockWebServer-" + raw.getRemoteSocketAddress(); 243b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson executor.execute(namedRunnable(name, new Runnable() { 244706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson int sequenceNumber = 0; 245b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 246b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson public void run() { 247b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson try { 248b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson processConnection(); 249b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } catch (Exception e) { 250b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.log(Level.WARNING, "MockWebServer connection failed", e); 251b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } 252b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } 253b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson 254b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson public void processConnection() throws Exception { 255706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson Socket socket; 256706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson if (sslSocketFactory != null) { 257706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson if (tunnelProxy) { 258c996149b500fc4825156106554457fe2394ae087Jesse Wilson createTunnel(); 259b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 260706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson socket = sslSocketFactory.createSocket( 261706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true); 262706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson ((SSLSocket) socket).setUseClientMode(false); 2638ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson openClientSockets.add(socket); 2648ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson openClientSockets.remove(raw); 265706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson } else { 266706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson socket = raw; 267b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 268b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 269706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson InputStream in = new BufferedInputStream(socket.getInputStream()); 270706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson OutputStream out = new BufferedOutputStream(socket.getOutputStream()); 271706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson 272b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson while (!responseQueue.isEmpty() && processOneRequest(in, out, socket)) {} 273b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson 274b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson if (sequenceNumber == 0) { 275b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson logger.warning("MockWebServer connection didn't make a request"); 276706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson } 277706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson 278b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson in.close(); 279b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.close(); 2808ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson socket.close(); 281b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson if (responseQueue.isEmpty()) { 282b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson shutdown(); 283b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } 2848ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson openClientSockets.remove(socket); 285b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 286706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson 287706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson /** 288c996149b500fc4825156106554457fe2394ae087Jesse Wilson * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response 289c996149b500fc4825156106554457fe2394ae087Jesse Wilson * is dispatched. 290c996149b500fc4825156106554457fe2394ae087Jesse Wilson */ 291c996149b500fc4825156106554457fe2394ae087Jesse Wilson private void createTunnel() throws IOException, InterruptedException { 292c996149b500fc4825156106554457fe2394ae087Jesse Wilson while (true) { 293c996149b500fc4825156106554457fe2394ae087Jesse Wilson MockResponse connect = responseQueue.peek(); 294c996149b500fc4825156106554457fe2394ae087Jesse Wilson if (!processOneRequest(raw.getInputStream(), raw.getOutputStream(), raw)) { 295c996149b500fc4825156106554457fe2394ae087Jesse Wilson throw new IllegalStateException("Tunnel without any CONNECT!"); 296c996149b500fc4825156106554457fe2394ae087Jesse Wilson } 297c996149b500fc4825156106554457fe2394ae087Jesse Wilson if (connect.getSocketPolicy() == SocketPolicy.UPGRADE_TO_SSL_AT_END) { 298c996149b500fc4825156106554457fe2394ae087Jesse Wilson return; 299c996149b500fc4825156106554457fe2394ae087Jesse Wilson } 300c996149b500fc4825156106554457fe2394ae087Jesse Wilson } 301c996149b500fc4825156106554457fe2394ae087Jesse Wilson } 302c996149b500fc4825156106554457fe2394ae087Jesse Wilson 303c996149b500fc4825156106554457fe2394ae087Jesse Wilson /** 304706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * Reads a request and writes its response. Returns true if a request 305706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson * was processed. 306706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson */ 307e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson private boolean processOneRequest(InputStream in, OutputStream out, Socket socket) 308706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson throws IOException, InterruptedException { 309706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson RecordedRequest request = readRequest(in, sequenceNumber); 310706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson if (request == null) { 311706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson return false; 312706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson } 313096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson MockResponse response = dispatch(request); 31451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson writeResponse(out, response); 315e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) { 31651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson in.close(); 31751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson out.close(); 318e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) { 319e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson socket.shutdownInput(); 320e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) { 321e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson socket.shutdownOutput(); 32251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson } 323706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson sequenceNumber++; 324706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson return true; 325706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson } 326096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson })); 327b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 328b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 329b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 330b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * @param sequenceNumber the index of this request on this connection. 331b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 332b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException { 333b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson String request; 334b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson try { 335b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson request = readAsciiUntilCrlf(in); 336b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } catch (IOException streamIsClosed) { 337b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson return null; // no request because we closed the stream 338b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson } 339b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (request.isEmpty()) { 340b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson return null; // no request because the stream is exhausted 341b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 342b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 343b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson List<String> headers = new ArrayList<String>(); 344b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson int contentLength = -1; 345b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson boolean chunked = false; 346b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson String header; 347b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson while (!(header = readAsciiUntilCrlf(in)).isEmpty()) { 348b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson headers.add(header); 349b274574b7e2681f4411852af9857b4540bf75841Elliott Hughes String lowercaseHeader = header.toLowerCase(Locale.ROOT); 350b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) { 351b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson contentLength = Integer.parseInt(header.substring(15).trim()); 352b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 353b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (lowercaseHeader.startsWith("transfer-encoding:") && 354b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson lowercaseHeader.substring(18).trim().equals("chunked")) { 355b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson chunked = true; 356b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 357b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 358b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 359fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson boolean hasBody = false; 360b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson TruncatingOutputStream requestBody = new TruncatingOutputStream(); 361b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson List<Integer> chunkSizes = new ArrayList<Integer>(); 362b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (contentLength != -1) { 363fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson hasBody = true; 364b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson transfer(contentLength, in, requestBody); 365b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } else if (chunked) { 366fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson hasBody = true; 367b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson while (true) { 368b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16); 369b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (chunkSize == 0) { 370b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson readEmptyLine(in); 371b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson break; 372b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 373b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson chunkSizes.add(chunkSize); 374b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson transfer(chunkSize, in, requestBody); 375b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson readEmptyLine(in); 376b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 377b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 378b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 3790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson if (request.startsWith("OPTIONS ") || request.startsWith("GET ") 3800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson || request.startsWith("HEAD ") || request.startsWith("DELETE ") 381adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson || request .startsWith("TRACE ") || request.startsWith("CONNECT ")) { 382fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson if (hasBody) { 3830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson throw new IllegalArgumentException("Request must not have a body: " + request); 384fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson } 3850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson } else if (request.startsWith("POST ") || request.startsWith("PUT ")) { 386fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson if (!hasBody) { 3870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson throw new IllegalArgumentException("Request must have a body: " + request); 388fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson } 389fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson } else { 390fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson throw new UnsupportedOperationException("Unexpected method: " + request); 391fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson } 392fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson 393b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return new RecordedRequest(request, headers, chunkSizes, 394b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber); 395b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 396b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 397b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 398b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Returns a response to satisfy {@code request}. 399b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 400096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson private MockResponse dispatch(RecordedRequest request) throws InterruptedException { 401211d3bbada505912bb16e9d1a6c1a9f1f5c16cffJesse Wilson if (responseQueue.isEmpty()) { 402b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson throw new IllegalStateException("Unexpected request: " + request); 403b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 404096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson 4050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson // to permit interactive/browser testing, ignore requests for favicons 4060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson if (request.getRequestLine().equals("GET /favicon.ico HTTP/1.1")) { 4070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson System.out.println("served " + request.getRequestLine()); 4080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson return new MockResponse() 4090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND); 4100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson } 4110c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson 412096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson if (singleResponse) { 413096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson return responseQueue.peek(); 414096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } else { 415096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson requestCount.incrementAndGet(); 416096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson requestQueue.add(request); 417096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson return responseQueue.take(); 418096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 419b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 420b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 421b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private void writeResponse(OutputStream out, MockResponse response) throws IOException { 422b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.write((response.getStatus() + "\r\n").getBytes(ASCII)); 423b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson for (String header : response.getHeaders()) { 424b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.write((header + "\r\n").getBytes(ASCII)); 425b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 426b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.write(("\r\n").getBytes(ASCII)); 427b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.write(response.getBody()); 428b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.flush(); 429b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 430b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 431b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 432b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Transfer bytes from {@code in} to {@code out} until either {@code length} 433b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * bytes have been transferred or {@code in} is exhausted. 434b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 435b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private void transfer(int length, InputStream in, OutputStream out) throws IOException { 436b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson byte[] buffer = new byte[1024]; 437b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson while (length > 0) { 438b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson int count = in.read(buffer, 0, Math.min(buffer.length, length)); 439b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (count == -1) { 440b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return; 441b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 442b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson out.write(buffer, 0, count); 443b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson length -= count; 444b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 445b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 446b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 447b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 448b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Returns the text from {@code in} until the next "\r\n", or null if 449b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * {@code in} is exhausted. 450b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 451b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private String readAsciiUntilCrlf(InputStream in) throws IOException { 452b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson StringBuilder builder = new StringBuilder(); 453b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson while (true) { 454b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson int c = in.read(); 455b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') { 456b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson builder.deleteCharAt(builder.length() - 1); 457b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return builder.toString(); 458b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } else if (c == -1) { 459b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson return builder.toString(); 460b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } else { 461b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson builder.append((char) c); 462b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 463b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 464b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 465b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 466b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private void readEmptyLine(InputStream in) throws IOException { 467b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson String line = readAsciiUntilCrlf(in); 468b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (!line.isEmpty()) { 469b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson throw new IllegalStateException("Expected empty but was: " + line); 470b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 471b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 472b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 473b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson /** 474b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * An output stream that drops data after bodyLimit bytes. 475b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */ 476b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private class TruncatingOutputStream extends ByteArrayOutputStream { 477b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private int numBytesReceived = 0; 478b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson @Override public void write(byte[] buffer, int offset, int len) { 479b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson numBytesReceived += len; 480b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson super.write(buffer, offset, Math.min(len, bodyLimit - count)); 481b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 482b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson @Override public void write(int oneByte) { 483b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson numBytesReceived++; 484b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (count < bodyLimit) { 485b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson super.write(oneByte); 486b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 487b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 488b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 489096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson 490b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson private static Runnable namedRunnable(final String name, final Runnable runnable) { 491b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson return new Runnable() { 492b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson public void run() { 493096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson String originalName = Thread.currentThread().getName(); 494096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson Thread.currentThread().setName(name); 495096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson try { 496b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson runnable.run(); 497096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } finally { 498096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson Thread.currentThread().setName(originalName); 499096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 500096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 501096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson }; 502096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson } 503fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson} 504