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; 2054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.BufferedInputStream; 2154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.BufferedOutputStream; 2254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.ByteArrayOutputStream; 2354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.IOException; 2454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.InputStream; 2554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.OutputStream; 2654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.HttpURLConnection; 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; 3654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.ArrayList; 3754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Collections; 3854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Iterator; 3954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.List; 4054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Set; 4154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.BlockingQueue; 4254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ConcurrentHashMap; 4354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ExecutorService; 4454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.Executors; 4554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.LinkedBlockingDeque; 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; 5054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocket; 5154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocketFactory; 5254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 5354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson/** 5454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * A scriptable web server. Callers supply canned responses and the server 5554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * replays them upon request in sequence. 5654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 5754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonpublic final class MockWebServer { 5854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 5954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson static final String ASCII = "US-ASCII"; 6054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 6154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private static final Logger logger = Logger.getLogger(MockWebServer.class.getName()); 6254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private final BlockingQueue<RecordedRequest> requestQueue 6354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson = new LinkedBlockingQueue<RecordedRequest>(); 6454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private final BlockingQueue<MockResponse> responseQueue 6554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson = new LinkedBlockingDeque<MockResponse>(); 6654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private final Set<Socket> openClientSockets 6754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson = Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>()); 6854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private boolean singleResponse; 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; 7554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 7654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private int port = -1; 7754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 7854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public int getPort() { 7954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (port == -1) { 8054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalStateException("Cannot retrieve port before calling play()"); 8154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 8254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return port; 8354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 8454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 8554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public String getHostName() { 8654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 8754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return InetAddress.getLocalHost().getHostName(); 8854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (UnknownHostException e) { 8954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new AssertionError(); 9054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 9154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 9254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 9354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public Proxy toProxyAddress() { 9454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getHostName(), getPort())); 9554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 9654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 9754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 9854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Returns a URL for connecting to this server. 9954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 10054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * @param path the request path, such as "/". 10154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 10254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public URL getUrl(String path) throws MalformedURLException, UnknownHostException { 10354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return sslSocketFactory != null 10454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson ? new URL("https://" + getHostName() + ":" + getPort() + path) 10554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson : new URL("http://" + getHostName() + ":" + getPort() + path); 10654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 10754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 10854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 10954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Sets the number of bytes of the POST body to keep in memory to the given 11054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * limit. 11154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 11254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void setBodyLimit(int maxBodyLength) { 11354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.bodyLimit = maxBodyLength; 11454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 11554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 11654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 11754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Serve requests with HTTPS rather than otherwise. 11854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 11954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * @param tunnelProxy whether to expect the HTTP CONNECT method before 12054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * negotiating TLS. 12154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 12254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) { 12354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.sslSocketFactory = sslSocketFactory; 12454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.tunnelProxy = tunnelProxy; 12554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 12654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 12754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 12854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Awaits the next HTTP request, removes it, and returns it. Callers should 12954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * use this to verify the request sent was as intended. 13054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 13154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public RecordedRequest takeRequest() throws InterruptedException { 13254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return requestQueue.take(); 13354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 13454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 13554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 13654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Returns the number of HTTP requests received thus far by this server. 13754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * This may exceed the number of HTTP connections when connection reuse is 13854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * in practice. 13954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 14054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public int getRequestCount() { 14154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return requestCount.get(); 14254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 14354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 14454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void enqueue(MockResponse response) { 14554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson responseQueue.add(response.clone()); 14654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 14754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 14854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 14954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * By default, this class processes requests coming in by adding them to a 15054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * queue and serves responses by removing them from another queue. This mode 15154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * is appropriate for correctness testing. 15254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 15354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * <p>Serving a single response causes the server to be stateless: requests 15454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * are not enqueued, and responses are not dequeued. This mode is appropriate 15554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * for benchmarking. 15654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 15754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void setSingleResponse(boolean singleResponse) { 15854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.singleResponse = singleResponse; 15954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 16054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 16154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 16254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Equivalent to {@code play(0)}. 16354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 16454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void play() throws IOException { 16554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson play(0); 16654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 16754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 16854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 16954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Starts the server, serves all enqueued requests, and shuts the server 17054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * down. 17154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 17254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * @param port the port to listen to, or 0 for any available port. 17354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Automated tests should always use port 0 to avoid flakiness when a 17454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * specific port is unavailable. 17554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 17654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void play(int port) throws IOException { 17754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson executor = Executors.newCachedThreadPool(); 17854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket = new ServerSocket(port); 17954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket.setReuseAddress(true); 18054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 18154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.port = serverSocket.getLocalPort(); 18254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() { 18354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void run() { 18454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 18554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson acceptConnections(); 18654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 18754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer connection failed", e); 18854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 18954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 19054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /* 19154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * This gnarly block of code will release all sockets and 19254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * all thread, even if any close fails. 19354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 19454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 19554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket.close(); 19654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 19754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer server socket close failed", e); 19854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 19954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext();) { 20054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 20154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson s.next().close(); 20254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson s.remove(); 20354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 20454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer socket close failed", e); 20554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 20654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 20754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 20854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson executor.shutdown(); 20954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 21054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer executor shutdown failed", e); 21154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 21254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 21354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 21454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void acceptConnections() throws Exception { 21554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson do { 21654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Socket socket; 21754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 21854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket = serverSocket.accept(); 21954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (SocketException ignored) { 22054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson continue; 22154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 22254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson MockResponse peek = responseQueue.peek(); 22354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (peek != null && peek.getSocketPolicy() == DISCONNECT_AT_START) { 22454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson responseQueue.take(); 22554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.close(); 22654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 22754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson openClientSockets.add(socket); 22854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serveConnection(socket); 22954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 23054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } while (!responseQueue.isEmpty()); 23154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 23254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson })); 23354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 23454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 23554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void shutdown() throws IOException { 23654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (serverSocket != null) { 23754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket.close(); // should cause acceptConnections() to break out 23854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 23954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 24054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 24154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void serveConnection(final Socket raw) { 24254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String name = "MockWebServer-" + raw.getRemoteSocketAddress(); 24354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson executor.execute(namedRunnable(name, new Runnable() { 24454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int sequenceNumber = 0; 24554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 24654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void run() { 24754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 24854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson processConnection(); 24954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Exception e) { 25054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer connection failed", e); 25154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 25254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 25354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 25454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void processConnection() throws Exception { 25554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Socket socket; 25654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (sslSocketFactory != null) { 25754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (tunnelProxy) { 25854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson createTunnel(); 25954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 26054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket = sslSocketFactory.createSocket( 26154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true); 26254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson ((SSLSocket) socket).setUseClientMode(false); 26354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson openClientSockets.add(socket); 26454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson openClientSockets.remove(raw); 26554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 26654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket = raw; 26754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 26854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 26954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson InputStream in = new BufferedInputStream(socket.getInputStream()); 27054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson OutputStream out = new BufferedOutputStream(socket.getOutputStream()); 27154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 27254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (!responseQueue.isEmpty() && processOneRequest(in, out, socket)) {} 27354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 27454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (sequenceNumber == 0) { 27554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.warning("MockWebServer connection didn't make a request"); 27654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 27754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 27854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson in.close(); 27954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.close(); 28054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.close(); 28154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (responseQueue.isEmpty()) { 28254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson shutdown(); 28354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 28454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson openClientSockets.remove(socket); 28554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 28654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 28754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 28854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response 28954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * is dispatched. 29054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 29154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void createTunnel() throws IOException, InterruptedException { 29254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (true) { 29354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson MockResponse connect = responseQueue.peek(); 29454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (!processOneRequest(raw.getInputStream(), raw.getOutputStream(), raw)) { 29554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalStateException("Tunnel without any CONNECT!"); 29654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 29754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (connect.getSocketPolicy() == SocketPolicy.UPGRADE_TO_SSL_AT_END) { 29854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return; 29954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 30054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 30154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 30254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 30354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 30454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Reads a request and writes its response. Returns true if a request 30554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * was processed. 30654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 30754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private boolean processOneRequest(InputStream in, OutputStream out, Socket socket) 30854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throws IOException, InterruptedException { 30954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson RecordedRequest request = readRequest(in, sequenceNumber); 31054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (request == null) { 31154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return false; 31254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 31354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson MockResponse response = dispatch(request); 31454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson writeResponse(out, response); 31554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) { 31654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson in.close(); 31754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.close(); 31854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) { 31954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.shutdownInput(); 32054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) { 32154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.shutdownOutput(); 32254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 32354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson sequenceNumber++; 32454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return true; 32554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 32654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson })); 32754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 32854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 32954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 33054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * @param sequenceNumber the index of this request on this connection. 33154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 33254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException { 33354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String request; 33454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 33554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson request = readAsciiUntilCrlf(in); 33654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (IOException streamIsClosed) { 33754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return null; // no request because we closed the stream 33854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 33954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (request.isEmpty()) { 34054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return null; // no request because the stream is exhausted 34154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 34254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 34354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson List<String> headers = new ArrayList<String>(); 34454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int contentLength = -1; 34554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson boolean chunked = false; 34654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String header; 34754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (!(header = readAsciiUntilCrlf(in)).isEmpty()) { 34854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson headers.add(header); 34954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String lowercaseHeader = header.toLowerCase(); 35054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) { 35154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson contentLength = Integer.parseInt(header.substring(15).trim()); 35254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 35354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (lowercaseHeader.startsWith("transfer-encoding:") && 35454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson lowercaseHeader.substring(18).trim().equals("chunked")) { 35554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson chunked = true; 35654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 35754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 35854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 35954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson boolean hasBody = false; 36054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson TruncatingOutputStream requestBody = new TruncatingOutputStream(); 36154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson List<Integer> chunkSizes = new ArrayList<Integer>(); 36254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (contentLength != -1) { 36354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson hasBody = true; 36454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson transfer(contentLength, in, requestBody); 36554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (chunked) { 36654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson hasBody = true; 36754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (true) { 36854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16); 36954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (chunkSize == 0) { 37054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson readEmptyLine(in); 37154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson break; 37254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 37354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson chunkSizes.add(chunkSize); 37454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson transfer(chunkSize, in, requestBody); 37554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson readEmptyLine(in); 37654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 37754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 37854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 37954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (request.startsWith("OPTIONS ") || request.startsWith("GET ") 38054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson || request.startsWith("HEAD ") || request.startsWith("DELETE ") 38154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson || request .startsWith("TRACE ") || request.startsWith("CONNECT ")) { 38254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (hasBody) { 38354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalArgumentException("Request must not have a body: " + request); 38454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 38554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (request.startsWith("POST ") || request.startsWith("PUT ")) { 38654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (!hasBody) { 38754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalArgumentException("Request must have a body: " + request); 38854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 38954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 39054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new UnsupportedOperationException("Unexpected method: " + request); 39154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 39254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 39354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return new RecordedRequest(request, headers, chunkSizes, 39454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber); 39554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 39654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 39754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 39854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Returns a response to satisfy {@code request}. 39954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 40054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private MockResponse dispatch(RecordedRequest request) throws InterruptedException { 40154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (responseQueue.isEmpty()) { 40254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalStateException("Unexpected request: " + request); 40354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 40454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 40554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson // to permit interactive/browser testing, ignore requests for favicons 40654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (request.getRequestLine().equals("GET /favicon.ico HTTP/1.1")) { 40754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson System.out.println("served " + request.getRequestLine()); 40854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return new MockResponse() 40954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND); 41054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 41154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 41254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (singleResponse) { 41354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return responseQueue.peek(); 41454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 41554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson requestCount.incrementAndGet(); 41654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson requestQueue.add(request); 41754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return responseQueue.take(); 41854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 41954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 42054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 42154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void writeResponse(OutputStream out, MockResponse response) throws IOException { 42254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.write((response.getStatus() + "\r\n").getBytes(ASCII)); 42354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson for (String header : response.getHeaders()) { 42454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.write((header + "\r\n").getBytes(ASCII)); 42554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 42654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.write(("\r\n").getBytes(ASCII)); 42754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.write(response.getBody()); 42854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.flush(); 42954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 43054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 43154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 43254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Transfer bytes from {@code in} to {@code out} until either {@code length} 43354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * bytes have been transferred or {@code in} is exhausted. 43454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 43554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void transfer(int length, InputStream in, OutputStream out) throws IOException { 43654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson byte[] buffer = new byte[1024]; 43754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (length > 0) { 43854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int count = in.read(buffer, 0, Math.min(buffer.length, length)); 43954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (count == -1) { 44054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return; 44154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 44254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.write(buffer, 0, count); 44354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson length -= count; 44454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 44554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 44654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 44754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 44854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Returns the text from {@code in} until the next "\r\n", or null if 44954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * {@code in} is exhausted. 45054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 45154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private String readAsciiUntilCrlf(InputStream in) throws IOException { 45254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson StringBuilder builder = new StringBuilder(); 45354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (true) { 45454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int c = in.read(); 45554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') { 45654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson builder.deleteCharAt(builder.length() - 1); 45754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return builder.toString(); 45854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (c == -1) { 45954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return builder.toString(); 46054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 46154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson builder.append((char) c); 46254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 46354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 46454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 46554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 46654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void readEmptyLine(InputStream in) throws IOException { 46754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String line = readAsciiUntilCrlf(in); 46854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (!line.isEmpty()) { 46954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalStateException("Expected empty but was: " + line); 47054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 47154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 47254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 47354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 47454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * An output stream that drops data after bodyLimit bytes. 47554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 47654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private class TruncatingOutputStream extends ByteArrayOutputStream { 47754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private int numBytesReceived = 0; 47854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson @Override public void write(byte[] buffer, int offset, int len) { 47954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson numBytesReceived += len; 48054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson super.write(buffer, offset, Math.min(len, bodyLimit - count)); 48154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 48254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson @Override public void write(int oneByte) { 48354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson numBytesReceived++; 48454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (count < bodyLimit) { 48554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson super.write(oneByte); 48654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 48754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 48854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 48954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 49054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private static Runnable namedRunnable(final String name, final Runnable runnable) { 49154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return new Runnable() { 49254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void run() { 49354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String originalName = Thread.currentThread().getName(); 49454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Thread.currentThread().setName(name); 49554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 49654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson runnable.run(); 49754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } finally { 49854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Thread.currentThread().setName(originalName); 49954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 50054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 50154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson }; 50254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 50354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson} 504