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; 213b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey 2254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.BufferedInputStream; 2354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.BufferedOutputStream; 2454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.ByteArrayOutputStream; 2554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.IOException; 2654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.InputStream; 2754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.OutputStream; 2854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.HttpURLConnection; 2954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.InetAddress; 3054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.InetSocketAddress; 3154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.MalformedURLException; 3254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.Proxy; 3354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.ServerSocket; 3454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.Socket; 3554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.SocketException; 3654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.URL; 3754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.UnknownHostException; 3824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport java.security.cert.CertificateException; 3924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport java.security.cert.X509Certificate; 4054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.ArrayList; 4154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Collections; 4254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Iterator; 4354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.List; 4454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Set; 4554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.BlockingQueue; 4654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ConcurrentHashMap; 4754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ExecutorService; 4854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.Executors; 4954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.LinkedBlockingDeque; 5054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.LinkedBlockingQueue; 5154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.atomic.AtomicInteger; 5254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.logging.Level; 5354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.logging.Logger; 5424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.SSLContext; 5554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocket; 5654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocketFactory; 5724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.TrustManager; 5824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.X509TrustManager; 5954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 6054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson/** 6154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * A scriptable web server. Callers supply canned responses and the server 6254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * replays them upon request in sequence. 6354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 6454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonpublic final class MockWebServer { 6554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 6654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson static final String ASCII = "US-ASCII"; 6754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 6854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private static final Logger logger = Logger.getLogger(MockWebServer.class.getName()); 6954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private final BlockingQueue<RecordedRequest> requestQueue 7054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson = new LinkedBlockingQueue<RecordedRequest>(); 7154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private final BlockingQueue<MockResponse> responseQueue 7254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson = new LinkedBlockingDeque<MockResponse>(); 7354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private final Set<Socket> openClientSockets 7454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson = Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>()); 7554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private boolean singleResponse; 7654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private final AtomicInteger requestCount = new AtomicInteger(); 7754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private int bodyLimit = Integer.MAX_VALUE; 7854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private ServerSocket serverSocket; 7954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private SSLSocketFactory sslSocketFactory; 8054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private ExecutorService executor; 8154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private boolean tunnelProxy; 8254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 8354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private int port = -1; 8454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 8554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public int getPort() { 8654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (port == -1) { 8754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalStateException("Cannot retrieve port before calling play()"); 8854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 8954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return port; 9054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 9154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 9254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public String getHostName() { 9354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 9454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return InetAddress.getLocalHost().getHostName(); 9554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (UnknownHostException e) { 9654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new AssertionError(); 9754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 9854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 9954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 10054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public Proxy toProxyAddress() { 10154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getHostName(), getPort())); 10254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 10354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 10454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 10554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Returns a URL for connecting to this server. 10654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 10754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * @param path the request path, such as "/". 10854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 10954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public URL getUrl(String path) throws MalformedURLException, UnknownHostException { 11054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return sslSocketFactory != null 11154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson ? new URL("https://" + getHostName() + ":" + getPort() + path) 11254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson : new URL("http://" + getHostName() + ":" + getPort() + path); 11354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 11454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 11554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 11618819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson * Returns a cookie domain for this server. This returns the server's 11718819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson * non-loopback host name if it is known. Otherwise this returns ".local" 11818819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson * for this server's loopback name. 11918819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson */ 12018819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson public String getCookieDomain() { 12118819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson String hostName = getHostName(); 12218819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson return hostName.contains(".") ? hostName : ".local"; 12318819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson } 12418819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson 12518819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson /** 12654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Sets the number of bytes of the POST body to keep in memory to the given 12754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * limit. 12854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 12954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void setBodyLimit(int maxBodyLength) { 13054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.bodyLimit = maxBodyLength; 13154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 13254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 13354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 13454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Serve requests with HTTPS rather than otherwise. 13554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 13654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * @param tunnelProxy whether to expect the HTTP CONNECT method before 13754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * negotiating TLS. 13854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 13954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) { 14054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.sslSocketFactory = sslSocketFactory; 14154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.tunnelProxy = tunnelProxy; 14254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 14354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 14454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 14554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Awaits the next HTTP request, removes it, and returns it. Callers should 14654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * use this to verify the request sent was as intended. 14754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 14854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public RecordedRequest takeRequest() throws InterruptedException { 14954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return requestQueue.take(); 15054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 15154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 15254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 15354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Returns the number of HTTP requests received thus far by this server. 15454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * This may exceed the number of HTTP connections when connection reuse is 15554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * in practice. 15654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 15754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public int getRequestCount() { 15854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return requestCount.get(); 15954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 16054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 16154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void enqueue(MockResponse response) { 16254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson responseQueue.add(response.clone()); 16354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 16454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 16554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 16654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * By default, this class processes requests coming in by adding them to a 16754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * queue and serves responses by removing them from another queue. This mode 16854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * is appropriate for correctness testing. 16954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 17054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * <p>Serving a single response causes the server to be stateless: requests 17154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * are not enqueued, and responses are not dequeued. This mode is appropriate 17254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * for benchmarking. 17354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 17454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void setSingleResponse(boolean singleResponse) { 17554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.singleResponse = singleResponse; 17654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 17754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 17854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 17954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Equivalent to {@code play(0)}. 18054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 18154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void play() throws IOException { 18254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson play(0); 18354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 18454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 18554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 18654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Starts the server, serves all enqueued requests, and shuts the server 18754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * down. 18854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 18954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * @param port the port to listen to, or 0 for any available port. 19054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Automated tests should always use port 0 to avoid flakiness when a 19154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * specific port is unavailable. 19254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 19354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void play(int port) throws IOException { 19454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson executor = Executors.newCachedThreadPool(); 19554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket = new ServerSocket(port); 19654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket.setReuseAddress(true); 19754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 19854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.port = serverSocket.getLocalPort(); 19954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() { 20054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void run() { 20154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 20254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson acceptConnections(); 20354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 20454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer connection failed", e); 20554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 20654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 20754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /* 20854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * This gnarly block of code will release all sockets and 20954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * all thread, even if any close fails. 21054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 21154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 21254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket.close(); 21354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 21454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer server socket close failed", e); 21554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 21654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext();) { 21754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 21854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson s.next().close(); 21954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson s.remove(); 22054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 22154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer socket close failed", e); 22254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 22354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 22454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 22554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson executor.shutdown(); 22654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 22754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer executor shutdown failed", e); 22854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 22954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 23054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 23154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void acceptConnections() throws Exception { 23254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson do { 23354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Socket socket; 23454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 23554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket = serverSocket.accept(); 23654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (SocketException ignored) { 23754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson continue; 23854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 23954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson MockResponse peek = responseQueue.peek(); 24054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (peek != null && peek.getSocketPolicy() == DISCONNECT_AT_START) { 24154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson responseQueue.take(); 24254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.close(); 24354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 24454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson openClientSockets.add(socket); 24554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serveConnection(socket); 24654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 24754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } while (!responseQueue.isEmpty()); 24854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 24954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson })); 25054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 25154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 25254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void shutdown() throws IOException { 25354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (serverSocket != null) { 25454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket.close(); // should cause acceptConnections() to break out 25554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 25654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 25754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 25854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void serveConnection(final Socket raw) { 25954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String name = "MockWebServer-" + raw.getRemoteSocketAddress(); 26054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson executor.execute(namedRunnable(name, new Runnable() { 26154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int sequenceNumber = 0; 26254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 26354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void run() { 26454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 26554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson processConnection(); 26654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Exception e) { 26754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer connection failed", e); 26854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 26954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 27054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 27154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void processConnection() throws Exception { 27254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Socket socket; 27354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (sslSocketFactory != null) { 27454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (tunnelProxy) { 27554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson createTunnel(); 27654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 27724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson MockResponse response = responseQueue.peek(); 27824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson if (response != null && response.getSocketPolicy() == FAIL_HANDSHAKE) { 27924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson processHandshakeFailure(raw, sequenceNumber++); 28024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson return; 28124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson } 28254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket = sslSocketFactory.createSocket( 28354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true); 28454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson ((SSLSocket) socket).setUseClientMode(false); 28554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson openClientSockets.add(socket); 28654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson openClientSockets.remove(raw); 28754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 28854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket = raw; 28954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 29054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 29154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson InputStream in = new BufferedInputStream(socket.getInputStream()); 29254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson OutputStream out = new BufferedOutputStream(socket.getOutputStream()); 29354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 29424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson while (!responseQueue.isEmpty() && processOneRequest(socket, in, out)) {} 29554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 29654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (sequenceNumber == 0) { 29754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.warning("MockWebServer connection didn't make a request"); 29854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 29954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 30054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson in.close(); 30154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.close(); 30254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.close(); 30354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (responseQueue.isEmpty()) { 30454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson shutdown(); 30554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 30654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson openClientSockets.remove(socket); 30754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 30854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 30954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 31054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response 31154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * is dispatched. 31254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 31354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void createTunnel() throws IOException, InterruptedException { 31454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (true) { 31554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson MockResponse connect = responseQueue.peek(); 31624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson if (!processOneRequest(raw, raw.getInputStream(), raw.getOutputStream())) { 31754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalStateException("Tunnel without any CONNECT!"); 31854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 31954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (connect.getSocketPolicy() == SocketPolicy.UPGRADE_TO_SSL_AT_END) { 32054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return; 32154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 32254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 32354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 32454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 32554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 32654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Reads a request and writes its response. Returns true if a request 32754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * was processed. 32854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 32924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson private boolean processOneRequest(Socket socket, InputStream in, OutputStream out) 33054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throws IOException, InterruptedException { 33124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson RecordedRequest request = readRequest(socket, in, sequenceNumber); 33254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (request == null) { 33354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return false; 33454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 33554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson MockResponse response = dispatch(request); 33654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson writeResponse(out, response); 33754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) { 33854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson in.close(); 33954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.close(); 34054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) { 34154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.shutdownInput(); 34254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) { 34354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.shutdownOutput(); 34454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 3453b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey logger.info("Received request: " + request + " and responded: " + response); 34654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson sequenceNumber++; 34754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return true; 34854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 34954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson })); 35054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 35154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 35224198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson private void processHandshakeFailure(Socket raw, int sequenceNumber) throws Exception { 35324198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson responseQueue.take(); 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(); 37724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson requestCount.incrementAndGet(); 37824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson requestQueue.add(new RecordedRequest(null, null, null, -1, null, sequenceNumber, socket)); 37924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson } 38024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson 38154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 38254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * @param sequenceNumber the index of this request on this connection. 38354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 38424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson private RecordedRequest readRequest(Socket socket, InputStream in, int sequenceNumber) 38524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson throws IOException { 38654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String request; 38754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 38854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson request = readAsciiUntilCrlf(in); 38954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (IOException streamIsClosed) { 39054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return null; // no request because we closed the stream 39154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 39254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (request.isEmpty()) { 39354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return null; // no request because the stream is exhausted 39454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 39554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 39654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson List<String> headers = new ArrayList<String>(); 39754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int contentLength = -1; 39854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson boolean chunked = false; 39954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String header; 40054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (!(header = readAsciiUntilCrlf(in)).isEmpty()) { 40154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson headers.add(header); 40254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String lowercaseHeader = header.toLowerCase(); 40354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) { 40454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson contentLength = Integer.parseInt(header.substring(15).trim()); 40554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 40654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (lowercaseHeader.startsWith("transfer-encoding:") && 40754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson lowercaseHeader.substring(18).trim().equals("chunked")) { 40854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson chunked = true; 40954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 41054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 41154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 41254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson boolean hasBody = false; 41354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson TruncatingOutputStream requestBody = new TruncatingOutputStream(); 41454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson List<Integer> chunkSizes = new ArrayList<Integer>(); 41554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (contentLength != -1) { 41654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson hasBody = true; 41754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson transfer(contentLength, in, requestBody); 41854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (chunked) { 41954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson hasBody = true; 42054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (true) { 42154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16); 42254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (chunkSize == 0) { 42354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson readEmptyLine(in); 42454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson break; 42554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 42654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson chunkSizes.add(chunkSize); 42754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson transfer(chunkSize, in, requestBody); 42854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson readEmptyLine(in); 42954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 43054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 43154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 43254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (request.startsWith("OPTIONS ") || request.startsWith("GET ") 43354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson || request.startsWith("HEAD ") || request.startsWith("DELETE ") 43454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson || request .startsWith("TRACE ") || request.startsWith("CONNECT ")) { 43554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (hasBody) { 43654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalArgumentException("Request must not have a body: " + request); 43754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 43854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (request.startsWith("POST ") || request.startsWith("PUT ")) { 43954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (!hasBody) { 44054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalArgumentException("Request must have a body: " + request); 44154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 44254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 44354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new UnsupportedOperationException("Unexpected method: " + request); 44454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 44554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 44654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return new RecordedRequest(request, headers, chunkSizes, 44724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber, socket); 44854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 44954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 45054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 45154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Returns a response to satisfy {@code request}. 45254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 45354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private MockResponse dispatch(RecordedRequest request) throws InterruptedException { 45454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (responseQueue.isEmpty()) { 45554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalStateException("Unexpected request: " + request); 45654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 45754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 45854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson // to permit interactive/browser testing, ignore requests for favicons 45954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (request.getRequestLine().equals("GET /favicon.ico HTTP/1.1")) { 46054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson System.out.println("served " + request.getRequestLine()); 46154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return new MockResponse() 46254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND); 46354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 46454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 46554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (singleResponse) { 46654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return responseQueue.peek(); 46754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 46854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson requestCount.incrementAndGet(); 46954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson requestQueue.add(request); 47054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return responseQueue.take(); 47154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 47254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 47354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 47454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void writeResponse(OutputStream out, MockResponse response) throws IOException { 47554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.write((response.getStatus() + "\r\n").getBytes(ASCII)); 47654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson for (String header : response.getHeaders()) { 47754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.write((header + "\r\n").getBytes(ASCII)); 47854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 47954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.write(("\r\n").getBytes(ASCII)); 48054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.flush(); 4813b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey 4823b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey byte[] body = response.getBody(); 4833b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey int bytesPerSecond = response.getBytesPerSecond(); 4843b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey 4853b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey for (int offset = 0; offset < body.length; offset += bytesPerSecond) { 4863b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey int count = Math.min(body.length - offset, bytesPerSecond); 4873b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey out.write(body, offset, count); 4883b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey out.flush(); 4893b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey 4903b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey if (offset + count < body.length) { 4913b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey try { 4923b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey Thread.sleep(1000); 4933b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey } catch (InterruptedException e) { 4943b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey throw new AssertionError(); 4953b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey } 4963b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey } 4973b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey } 49854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 49954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 50054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 50154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Transfer bytes from {@code in} to {@code out} until either {@code length} 50254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * bytes have been transferred or {@code in} is exhausted. 50354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 50454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void transfer(int length, InputStream in, OutputStream out) throws IOException { 50554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson byte[] buffer = new byte[1024]; 50654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (length > 0) { 50754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int count = in.read(buffer, 0, Math.min(buffer.length, length)); 50854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (count == -1) { 50954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return; 51054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 51154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.write(buffer, 0, count); 51254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson length -= count; 51354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 51454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 51554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 51654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 51754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Returns the text from {@code in} until the next "\r\n", or null if 51854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * {@code in} is exhausted. 51954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 52054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private String readAsciiUntilCrlf(InputStream in) throws IOException { 52154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson StringBuilder builder = new StringBuilder(); 52254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (true) { 52354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int c = in.read(); 52454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') { 52554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson builder.deleteCharAt(builder.length() - 1); 52654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return builder.toString(); 52754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (c == -1) { 52854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return builder.toString(); 52954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 53054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson builder.append((char) c); 53154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 53254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 53354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 53454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 53554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void readEmptyLine(InputStream in) throws IOException { 53654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String line = readAsciiUntilCrlf(in); 53754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (!line.isEmpty()) { 53854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalStateException("Expected empty but was: " + line); 53954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 54054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 54154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 54254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 54354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * An output stream that drops data after bodyLimit bytes. 54454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 54554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private class TruncatingOutputStream extends ByteArrayOutputStream { 54654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private int numBytesReceived = 0; 54754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson @Override public void write(byte[] buffer, int offset, int len) { 54854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson numBytesReceived += len; 54954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson super.write(buffer, offset, Math.min(len, bodyLimit - count)); 55054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 55154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson @Override public void write(int oneByte) { 55254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson numBytesReceived++; 55354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (count < bodyLimit) { 55454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson super.write(oneByte); 55554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 55654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 55754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 55854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 55954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private static Runnable namedRunnable(final String name, final Runnable runnable) { 56054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return new Runnable() { 56154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void run() { 56254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String originalName = Thread.currentThread().getName(); 56354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Thread.currentThread().setName(name); 56454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 56554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson runnable.run(); 56654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } finally { 56754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Thread.currentThread().setName(originalName); 56854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 56954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 57054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson }; 57154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 57254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson} 573