154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson/* 254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Copyright (C) 2011 Google Inc. 354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Licensed under the Apache License, Version 2.0 (the "License"); 554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * you may not use this file except in compliance with the License. 654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * You may obtain a copy of the License at 754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * http://www.apache.org/licenses/LICENSE-2.0 954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 1054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Unless required by applicable law or agreed to in writing, software 1154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * distributed under the License is distributed on an "AS IS" BASIS, 1254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * See the License for the specific language governing permissions and 1454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * limitations under the License. 1554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 1654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 1754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonpackage com.google.mockwebserver; 1854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 1954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START; 2024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport static com.google.mockwebserver.SocketPolicy.FAIL_HANDSHAKE; 2154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.BufferedInputStream; 2254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.BufferedOutputStream; 2354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.ByteArrayOutputStream; 2454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.IOException; 2554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.InputStream; 2654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.OutputStream; 2754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.InetAddress; 2854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.InetSocketAddress; 2954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.MalformedURLException; 3054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.Proxy; 3154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.ServerSocket; 3254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.Socket; 3354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.SocketException; 3454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.URL; 3554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.UnknownHostException; 3624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport java.security.cert.CertificateException; 3724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport java.security.cert.X509Certificate; 3854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.ArrayList; 3954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Iterator; 4054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.List; 417a68ed6a49c3060b235810391a82412a95f9c979jwilsonimport java.util.Map; 4254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.BlockingQueue; 4354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ConcurrentHashMap; 4454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ExecutorService; 4554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.Executors; 4654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.LinkedBlockingQueue; 4754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.atomic.AtomicInteger; 4854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.logging.Level; 4954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.logging.Logger; 5024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.SSLContext; 5154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocket; 5254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocketFactory; 5324198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.TrustManager; 5424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.X509TrustManager; 5554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 5654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson/** 5754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * A scriptable web server. Callers supply canned responses and the server 5854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * replays them upon request in sequence. 5954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 6054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonpublic final class MockWebServer { 6154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 6254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson static final String ASCII = "US-ASCII"; 6354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 6454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private static final Logger logger = Logger.getLogger(MockWebServer.class.getName()); 6554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private final BlockingQueue<RecordedRequest> requestQueue 6654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson = new LinkedBlockingQueue<RecordedRequest>(); 677a68ed6a49c3060b235810391a82412a95f9c979jwilson /** All map values are Boolean.TRUE. (Collections.newSetFromMap isn't available in Froyo) */ 687a68ed6a49c3060b235810391a82412a95f9c979jwilson private final Map<Socket, Boolean> openClientSockets = new ConcurrentHashMap<Socket, Boolean>(); 6954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private final AtomicInteger requestCount = new AtomicInteger(); 7054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private int bodyLimit = Integer.MAX_VALUE; 7154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private ServerSocket serverSocket; 7254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private SSLSocketFactory sslSocketFactory; 7354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private ExecutorService executor; 7454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private boolean tunnelProxy; 757a68ed6a49c3060b235810391a82412a95f9c979jwilson private Dispatcher dispatcher = new QueueDispatcher(); 7654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 7754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private int port = -1; 7854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 7954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public int getPort() { 8054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (port == -1) { 8154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalStateException("Cannot retrieve port before calling play()"); 8254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 8354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return port; 8454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 8554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 8654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public String getHostName() { 8754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 8854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return InetAddress.getLocalHost().getHostName(); 8954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (UnknownHostException e) { 9054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new AssertionError(); 9154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 9254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 9354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 9454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public Proxy toProxyAddress() { 9554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getHostName(), getPort())); 9654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 9754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 9854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 9954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Returns a URL for connecting to this server. 10054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 10154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * @param path the request path, such as "/". 10254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 1037a68ed6a49c3060b235810391a82412a95f9c979jwilson public URL getUrl(String path) { 1047a68ed6a49c3060b235810391a82412a95f9c979jwilson try { 1057a68ed6a49c3060b235810391a82412a95f9c979jwilson return sslSocketFactory != null 1067a68ed6a49c3060b235810391a82412a95f9c979jwilson ? new URL("https://" + getHostName() + ":" + getPort() + path) 1077a68ed6a49c3060b235810391a82412a95f9c979jwilson : new URL("http://" + getHostName() + ":" + getPort() + path); 1087a68ed6a49c3060b235810391a82412a95f9c979jwilson } catch (MalformedURLException e) { 1097a68ed6a49c3060b235810391a82412a95f9c979jwilson throw new AssertionError(e); 1107a68ed6a49c3060b235810391a82412a95f9c979jwilson } 11154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 11254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 11354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 11418819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson * Returns a cookie domain for this server. This returns the server's 11518819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson * non-loopback host name if it is known. Otherwise this returns ".local" 11618819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson * for this server's loopback name. 11718819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson */ 11818819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson public String getCookieDomain() { 11918819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson String hostName = getHostName(); 12018819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson return hostName.contains(".") ? hostName : ".local"; 12118819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson } 12218819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson 12318819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson /** 12454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Sets the number of bytes of the POST body to keep in memory to the given 12554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * limit. 12654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 12754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void setBodyLimit(int maxBodyLength) { 12854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.bodyLimit = maxBodyLength; 12954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 13054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 13154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 13254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Serve requests with HTTPS rather than otherwise. 13354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 13454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * @param tunnelProxy whether to expect the HTTP CONNECT method before 13554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * negotiating TLS. 13654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 13754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) { 13854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.sslSocketFactory = sslSocketFactory; 13954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.tunnelProxy = tunnelProxy; 14054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 14154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 14254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 14354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Awaits the next HTTP request, removes it, and returns it. Callers should 14454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * use this to verify the request sent was as intended. 14554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 14654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public RecordedRequest takeRequest() throws InterruptedException { 14754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return requestQueue.take(); 14854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 14954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 15054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 15154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Returns the number of HTTP requests received thus far by this server. 15254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * This may exceed the number of HTTP connections when connection reuse is 15354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * in practice. 15454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 15554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public int getRequestCount() { 15654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return requestCount.get(); 15754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 15854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 15954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 1607a68ed6a49c3060b235810391a82412a95f9c979jwilson * Scripts {@code response} to be returned to a request made in sequence. 1617a68ed6a49c3060b235810391a82412a95f9c979jwilson * The first request is served by the first enqueued response; the second 1627a68ed6a49c3060b235810391a82412a95f9c979jwilson * request by the second enqueued response; and so on. 16354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 1647a68ed6a49c3060b235810391a82412a95f9c979jwilson * @throws ClassCastException if the default dispatcher has been replaced 1657a68ed6a49c3060b235810391a82412a95f9c979jwilson * with {@link #setDispatcher(Dispatcher)}. 16654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 1677a68ed6a49c3060b235810391a82412a95f9c979jwilson public void enqueue(MockResponse response) { 1687a68ed6a49c3060b235810391a82412a95f9c979jwilson ((QueueDispatcher) dispatcher).enqueueResponse(response.clone()); 16954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 17054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 17154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 17254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Equivalent to {@code play(0)}. 17354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 17454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void play() throws IOException { 17554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson play(0); 17654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 17754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 17854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 17954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Starts the server, serves all enqueued requests, and shuts the server 18054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * down. 18154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 18254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * @param port the port to listen to, or 0 for any available port. 18354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Automated tests should always use port 0 to avoid flakiness when a 18454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * specific port is unavailable. 18554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 18654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void play(int port) throws IOException { 1876ae1753c9c0b3c0b2479a7c4099e38e4249dc50eBrian Carlstrom if (executor != null) { 1886ae1753c9c0b3c0b2479a7c4099e38e4249dc50eBrian Carlstrom throw new IllegalStateException("play() already called"); 1896ae1753c9c0b3c0b2479a7c4099e38e4249dc50eBrian Carlstrom } 19054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson executor = Executors.newCachedThreadPool(); 19154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket = new ServerSocket(port); 19254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket.setReuseAddress(true); 19354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 19454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.port = serverSocket.getLocalPort(); 19554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() { 19654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void run() { 19754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 19854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson acceptConnections(); 19954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 20054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer connection failed", e); 20154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 20254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 20354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /* 20454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * This gnarly block of code will release all sockets and 20554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * all thread, even if any close fails. 20654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 20754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 20854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket.close(); 20954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 21054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer server socket close failed", e); 21154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 2127a68ed6a49c3060b235810391a82412a95f9c979jwilson for (Iterator<Socket> s = openClientSockets.keySet().iterator(); s.hasNext(); ) { 21354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 21454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson s.next().close(); 21554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson s.remove(); 21654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 21754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer socket close failed", e); 21854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 21954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 22054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 22154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson executor.shutdown(); 22254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 22354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer executor shutdown failed", e); 22454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 22554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 22654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 22754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void acceptConnections() throws Exception { 2287a68ed6a49c3060b235810391a82412a95f9c979jwilson while (true) { 22954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Socket socket; 23054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 23154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket = serverSocket.accept(); 2327a68ed6a49c3060b235810391a82412a95f9c979jwilson } catch (SocketException e) { 2337a68ed6a49c3060b235810391a82412a95f9c979jwilson return; 23454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 2357a68ed6a49c3060b235810391a82412a95f9c979jwilson final SocketPolicy socketPolicy = dispatcher.peekSocketPolicy(); 2367a68ed6a49c3060b235810391a82412a95f9c979jwilson if (socketPolicy == DISCONNECT_AT_START) { 2377a68ed6a49c3060b235810391a82412a95f9c979jwilson dispatchBookkeepingRequest(0, socket); 23854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.close(); 23954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 2407a68ed6a49c3060b235810391a82412a95f9c979jwilson openClientSockets.put(socket, true); 24154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serveConnection(socket); 24254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 2437a68ed6a49c3060b235810391a82412a95f9c979jwilson } 24454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 24554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson })); 24654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 24754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 24854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void shutdown() throws IOException { 24954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (serverSocket != null) { 25054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket.close(); // should cause acceptConnections() to break out 25154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 25254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 25354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 25454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void serveConnection(final Socket raw) { 25554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String name = "MockWebServer-" + raw.getRemoteSocketAddress(); 25654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson executor.execute(namedRunnable(name, new Runnable() { 25754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int sequenceNumber = 0; 25854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 25954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void run() { 26054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 26154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson processConnection(); 26254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Exception e) { 26354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer connection failed", e); 26454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 26554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 26654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 26754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void processConnection() throws Exception { 26854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Socket socket; 26954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (sslSocketFactory != null) { 27054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (tunnelProxy) { 27154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson createTunnel(); 27254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 2737a68ed6a49c3060b235810391a82412a95f9c979jwilson final SocketPolicy socketPolicy = dispatcher.peekSocketPolicy(); 2747a68ed6a49c3060b235810391a82412a95f9c979jwilson if (socketPolicy == FAIL_HANDSHAKE) { 2757a68ed6a49c3060b235810391a82412a95f9c979jwilson dispatchBookkeepingRequest(sequenceNumber, raw); 27624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson processHandshakeFailure(raw, sequenceNumber++); 27724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson return; 27824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson } 27954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket = sslSocketFactory.createSocket( 28054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true); 28154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson ((SSLSocket) socket).setUseClientMode(false); 2827a68ed6a49c3060b235810391a82412a95f9c979jwilson openClientSockets.put(socket, true); 28354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson openClientSockets.remove(raw); 28454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 28554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket = raw; 28654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 28754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 28854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson InputStream in = new BufferedInputStream(socket.getInputStream()); 28954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson OutputStream out = new BufferedOutputStream(socket.getOutputStream()); 29054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 2917a68ed6a49c3060b235810391a82412a95f9c979jwilson while (processOneRequest(socket, in, out)) { 2927a68ed6a49c3060b235810391a82412a95f9c979jwilson } 29354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 29454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (sequenceNumber == 0) { 29554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.warning("MockWebServer connection didn't make a request"); 29654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 29754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 29854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson in.close(); 29954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.close(); 30054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.close(); 30154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson openClientSockets.remove(socket); 30254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 30354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 30454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 30554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response 30654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * is dispatched. 30754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 30854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void createTunnel() throws IOException, InterruptedException { 30954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (true) { 3107a68ed6a49c3060b235810391a82412a95f9c979jwilson final SocketPolicy socketPolicy = dispatcher.peekSocketPolicy(); 31124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson if (!processOneRequest(raw, raw.getInputStream(), raw.getOutputStream())) { 31254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalStateException("Tunnel without any CONNECT!"); 31354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 3147a68ed6a49c3060b235810391a82412a95f9c979jwilson if (socketPolicy == SocketPolicy.UPGRADE_TO_SSL_AT_END) { 31554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return; 31654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 31754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 31854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 31954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 32054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 32154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Reads a request and writes its response. Returns true if a request 32254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * was processed. 32354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 32424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson private boolean processOneRequest(Socket socket, InputStream in, OutputStream out) 32554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throws IOException, InterruptedException { 32624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson RecordedRequest request = readRequest(socket, in, sequenceNumber); 32754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (request == null) { 32854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return false; 32954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 3307a68ed6a49c3060b235810391a82412a95f9c979jwilson requestCount.incrementAndGet(); 3317a68ed6a49c3060b235810391a82412a95f9c979jwilson requestQueue.add(request); 3327a68ed6a49c3060b235810391a82412a95f9c979jwilson MockResponse response = dispatcher.dispatch(request); 333e44762f71ed6eeb718a0bb6f3848beb7696fd97dNarayan Kamath if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AFTER_READING_REQUEST) { 334e44762f71ed6eeb718a0bb6f3848beb7696fd97dNarayan Kamath logger.info("Received request: " + request + " and disconnected without responding"); 335e44762f71ed6eeb718a0bb6f3848beb7696fd97dNarayan Kamath return false; 336e44762f71ed6eeb718a0bb6f3848beb7696fd97dNarayan Kamath } 3377a68ed6a49c3060b235810391a82412a95f9c979jwilson writeResponse(out, response); 33854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) { 33954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson in.close(); 34054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.close(); 34154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) { 34254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.shutdownInput(); 34354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) { 34454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.shutdownOutput(); 34554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 3463b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey logger.info("Received request: " + request + " and responded: " + response); 34754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson sequenceNumber++; 34854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return true; 34954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 35054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson })); 35154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 35254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 35324198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson private void processHandshakeFailure(Socket raw, int sequenceNumber) throws Exception { 35424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson X509TrustManager untrusted = new X509TrustManager() { 35524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson @Override public void checkClientTrusted(X509Certificate[] chain, String authType) 35624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson throws CertificateException { 35724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson throw new CertificateException(); 35824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson } 35924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { 36024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson throw new AssertionError(); 36124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson } 36224198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson @Override public X509Certificate[] getAcceptedIssuers() { 36324198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson throw new AssertionError(); 36424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson } 36524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson }; 36624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson SSLContext context = SSLContext.getInstance("TLS"); 36724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson context.init(null, new TrustManager[] { untrusted }, new java.security.SecureRandom()); 36824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson SSLSocketFactory sslSocketFactory = context.getSocketFactory(); 36924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket( 37024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true); 37124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson try { 37224198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson socket.startHandshake(); // we're testing a handshake failure 37324198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson throw new AssertionError(); 37424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson } catch (IOException expected) { 37524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson } 37624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson socket.close(); 3777a68ed6a49c3060b235810391a82412a95f9c979jwilson } 3787a68ed6a49c3060b235810391a82412a95f9c979jwilson 3797a68ed6a49c3060b235810391a82412a95f9c979jwilson private void dispatchBookkeepingRequest(int sequenceNumber, Socket socket) throws InterruptedException { 38024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson requestCount.incrementAndGet(); 381cb8ab9cb9f5da083e7390920f0f7d0c8a879cb6bNarayan Kamath RecordedRequest request = new RecordedRequest(null, null, null, -1, null, sequenceNumber, 382cb8ab9cb9f5da083e7390920f0f7d0c8a879cb6bNarayan Kamath socket); 383cb8ab9cb9f5da083e7390920f0f7d0c8a879cb6bNarayan Kamath dispatcher.dispatch(request); 384cb8ab9cb9f5da083e7390920f0f7d0c8a879cb6bNarayan Kamath requestQueue.add(request); 38524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson } 38624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson 38754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 38854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * @param sequenceNumber the index of this request on this connection. 38954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 39024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson private RecordedRequest readRequest(Socket socket, InputStream in, int sequenceNumber) 39124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson throws IOException { 39254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String request; 39354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 39454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson request = readAsciiUntilCrlf(in); 39554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (IOException streamIsClosed) { 39654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return null; // no request because we closed the stream 39754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 3987a68ed6a49c3060b235810391a82412a95f9c979jwilson if (request.length() == 0) { 39954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return null; // no request because the stream is exhausted 40054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 40154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 40254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson List<String> headers = new ArrayList<String>(); 40354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int contentLength = -1; 40454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson boolean chunked = false; 40554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String header; 4067a68ed6a49c3060b235810391a82412a95f9c979jwilson while ((header = readAsciiUntilCrlf(in)).length() != 0) { 40754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson headers.add(header); 40854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String lowercaseHeader = header.toLowerCase(); 40954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) { 41054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson contentLength = Integer.parseInt(header.substring(15).trim()); 41154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 41254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (lowercaseHeader.startsWith("transfer-encoding:") && 41354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson lowercaseHeader.substring(18).trim().equals("chunked")) { 41454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson chunked = true; 41554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 41654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 41754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 41854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson boolean hasBody = false; 41954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson TruncatingOutputStream requestBody = new TruncatingOutputStream(); 42054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson List<Integer> chunkSizes = new ArrayList<Integer>(); 42154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (contentLength != -1) { 42254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson hasBody = true; 42354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson transfer(contentLength, in, requestBody); 42454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (chunked) { 42554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson hasBody = true; 42654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (true) { 42754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16); 42854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (chunkSize == 0) { 42954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson readEmptyLine(in); 43054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson break; 43154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 43254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson chunkSizes.add(chunkSize); 43354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson transfer(chunkSize, in, requestBody); 43454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson readEmptyLine(in); 43554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 43654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 43754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 43854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (request.startsWith("OPTIONS ") || request.startsWith("GET ") 43954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson || request.startsWith("HEAD ") || request.startsWith("DELETE ") 4407a68ed6a49c3060b235810391a82412a95f9c979jwilson || request.startsWith("TRACE ") || request.startsWith("CONNECT ")) { 44154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (hasBody) { 44254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalArgumentException("Request must not have a body: " + request); 44354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 4447a68ed6a49c3060b235810391a82412a95f9c979jwilson } else if (!request.startsWith("POST ") && !request.startsWith("PUT ")) { 44554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new UnsupportedOperationException("Unexpected method: " + request); 44654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 44754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 44854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return new RecordedRequest(request, headers, chunkSizes, 44924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber, socket); 45054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 45154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 4527a68ed6a49c3060b235810391a82412a95f9c979jwilson private void writeResponse(OutputStream out, MockResponse response) throws IOException { 4537a68ed6a49c3060b235810391a82412a95f9c979jwilson out.write((response.getStatus() + "\r\n").getBytes(ASCII)); 4547a68ed6a49c3060b235810391a82412a95f9c979jwilson for (String header : response.getHeaders()) { 4557a68ed6a49c3060b235810391a82412a95f9c979jwilson out.write((header + "\r\n").getBytes(ASCII)); 45654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 4577a68ed6a49c3060b235810391a82412a95f9c979jwilson out.write(("\r\n").getBytes(ASCII)); 4587a68ed6a49c3060b235810391a82412a95f9c979jwilson out.flush(); 45954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 4607a68ed6a49c3060b235810391a82412a95f9c979jwilson final InputStream in = response.getBodyStream(); 4617a68ed6a49c3060b235810391a82412a95f9c979jwilson if (in == null) { 4627a68ed6a49c3060b235810391a82412a95f9c979jwilson return; 46354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 4647a68ed6a49c3060b235810391a82412a95f9c979jwilson final int bytesPerSecond = response.getBytesPerSecond(); 46554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 4667a68ed6a49c3060b235810391a82412a95f9c979jwilson // Stream data in MTU-sized increments 4677a68ed6a49c3060b235810391a82412a95f9c979jwilson final byte[] buffer = new byte[1452]; 4687a68ed6a49c3060b235810391a82412a95f9c979jwilson final long delayMs; 4697a68ed6a49c3060b235810391a82412a95f9c979jwilson if (bytesPerSecond == Integer.MAX_VALUE) { 4707a68ed6a49c3060b235810391a82412a95f9c979jwilson delayMs = 0; 47154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 4727a68ed6a49c3060b235810391a82412a95f9c979jwilson delayMs = (1000 * buffer.length) / bytesPerSecond; 4737a68ed6a49c3060b235810391a82412a95f9c979jwilson } 4747a68ed6a49c3060b235810391a82412a95f9c979jwilson 4757a68ed6a49c3060b235810391a82412a95f9c979jwilson int read; 4767a68ed6a49c3060b235810391a82412a95f9c979jwilson long sinceDelay = 0; 4777a68ed6a49c3060b235810391a82412a95f9c979jwilson while ((read = in.read(buffer)) != -1) { 4787a68ed6a49c3060b235810391a82412a95f9c979jwilson out.write(buffer, 0, read); 4797a68ed6a49c3060b235810391a82412a95f9c979jwilson out.flush(); 4807a68ed6a49c3060b235810391a82412a95f9c979jwilson 4817a68ed6a49c3060b235810391a82412a95f9c979jwilson sinceDelay += read; 4827a68ed6a49c3060b235810391a82412a95f9c979jwilson if (sinceDelay >= buffer.length && delayMs > 0) { 4837a68ed6a49c3060b235810391a82412a95f9c979jwilson sinceDelay %= buffer.length; 4847a68ed6a49c3060b235810391a82412a95f9c979jwilson try { 4857a68ed6a49c3060b235810391a82412a95f9c979jwilson Thread.sleep(delayMs); 4867a68ed6a49c3060b235810391a82412a95f9c979jwilson } catch (InterruptedException e) { 4877a68ed6a49c3060b235810391a82412a95f9c979jwilson throw new AssertionError(); 4887a68ed6a49c3060b235810391a82412a95f9c979jwilson } 4897a68ed6a49c3060b235810391a82412a95f9c979jwilson } 49054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 49154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 49254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 49354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 49454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Transfer bytes from {@code in} to {@code out} until either {@code length} 49554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * bytes have been transferred or {@code in} is exhausted. 49654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 49754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void transfer(int length, InputStream in, OutputStream out) throws IOException { 49854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson byte[] buffer = new byte[1024]; 49954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (length > 0) { 50054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int count = in.read(buffer, 0, Math.min(buffer.length, length)); 50154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (count == -1) { 50254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return; 50354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 50454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.write(buffer, 0, count); 50554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson length -= count; 50654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 50754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 50854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 50954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 51054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Returns the text from {@code in} until the next "\r\n", or null if 51154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * {@code in} is exhausted. 51254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 51354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private String readAsciiUntilCrlf(InputStream in) throws IOException { 51454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson StringBuilder builder = new StringBuilder(); 51554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (true) { 51654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int c = in.read(); 51754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') { 51854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson builder.deleteCharAt(builder.length() - 1); 51954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return builder.toString(); 52054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (c == -1) { 52154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return builder.toString(); 52254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 52354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson builder.append((char) c); 52454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 52554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 52654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 52754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 52854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void readEmptyLine(InputStream in) throws IOException { 52954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String line = readAsciiUntilCrlf(in); 5307a68ed6a49c3060b235810391a82412a95f9c979jwilson if (line.length() != 0) { 53154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalStateException("Expected empty but was: " + line); 53254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 53354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 53454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 53554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 5367a68ed6a49c3060b235810391a82412a95f9c979jwilson * Sets the dispatcher used to match incoming requests to mock responses. 5377a68ed6a49c3060b235810391a82412a95f9c979jwilson * The default dispatcher simply serves a fixed sequence of responses from 5387a68ed6a49c3060b235810391a82412a95f9c979jwilson * a {@link #enqueue(MockResponse) queue}; custom dispatchers can vary the 5397a68ed6a49c3060b235810391a82412a95f9c979jwilson * response based on timing or the content of the request. 5407a68ed6a49c3060b235810391a82412a95f9c979jwilson */ 5417a68ed6a49c3060b235810391a82412a95f9c979jwilson public void setDispatcher(Dispatcher dispatcher) { 5427a68ed6a49c3060b235810391a82412a95f9c979jwilson if (dispatcher == null) { 5437a68ed6a49c3060b235810391a82412a95f9c979jwilson throw new NullPointerException(); 5447a68ed6a49c3060b235810391a82412a95f9c979jwilson } 5457a68ed6a49c3060b235810391a82412a95f9c979jwilson this.dispatcher = dispatcher; 5467a68ed6a49c3060b235810391a82412a95f9c979jwilson } 5477a68ed6a49c3060b235810391a82412a95f9c979jwilson 5487a68ed6a49c3060b235810391a82412a95f9c979jwilson /** 54954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * An output stream that drops data after bodyLimit bytes. 55054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 55154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private class TruncatingOutputStream extends ByteArrayOutputStream { 55254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private int numBytesReceived = 0; 55354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson @Override public void write(byte[] buffer, int offset, int len) { 55454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson numBytesReceived += len; 55554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson super.write(buffer, offset, Math.min(len, bodyLimit - count)); 55654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 55754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson @Override public void write(int oneByte) { 55854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson numBytesReceived++; 55954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (count < bodyLimit) { 56054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson super.write(oneByte); 56154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 56254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 56354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 56454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 56554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private static Runnable namedRunnable(final String name, final Runnable runnable) { 56654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return new Runnable() { 56754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void run() { 56854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String originalName = Thread.currentThread().getName(); 56954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Thread.currentThread().setName(name); 57054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 57154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson runnable.run(); 57254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } finally { 57354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Thread.currentThread().setName(originalName); 57454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 57554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 57654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson }; 57754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 57854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson} 579