1/*
2 * Copyright (C) 2013 Square, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.squareup.okhttp;
17
18import java.io.ByteArrayOutputStream;
19import java.io.IOException;
20import java.net.URL;
21import java.util.ArrayList;
22import java.util.Iterator;
23import java.util.LinkedHashMap;
24import java.util.List;
25import java.util.Map;
26import java.util.concurrent.TimeUnit;
27
28/**
29 * Records received HTTP responses so they can be later retrieved by tests.
30 */
31public class RecordingReceiver implements Response.Receiver {
32  public static final long TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
33
34  private final Map<Response, ByteArrayOutputStream> inFlightResponses
35      = new LinkedHashMap<Response, ByteArrayOutputStream>();
36  private final List<RecordedResponse> responses = new ArrayList<RecordedResponse>();
37
38  @Override public synchronized void onFailure(Failure failure) {
39    responses.add(new RecordedResponse(failure.request(), null, null, failure));
40    notifyAll();
41  }
42
43  @Override public synchronized boolean onResponse(Response response) throws IOException {
44    ByteArrayOutputStream out = inFlightResponses.get(response);
45    if (out == null) {
46      out = new ByteArrayOutputStream();
47      inFlightResponses.put(response, out);
48    }
49
50    byte[] buffer = new byte[1024];
51    Response.Body body = response.body();
52
53    while (body.ready()) {
54      int c = body.byteStream().read(buffer);
55
56      if (c == -1) {
57        inFlightResponses.remove(response);
58        responses.add(new RecordedResponse(
59            response.request(), response, out.toString("UTF-8"), null));
60        notifyAll();
61        return true;
62      }
63
64      out.write(buffer, 0, c);
65    }
66
67    return false;
68  }
69
70  /**
71   * Returns the recorded response triggered by {@code request}. Throws if the
72   * response isn't enqueued before the timeout.
73   */
74  public synchronized RecordedResponse await(URL url) throws Exception {
75    long timeoutMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) + TIMEOUT_MILLIS;
76    while (true) {
77      for (Iterator<RecordedResponse> i = responses.iterator(); i.hasNext(); ) {
78        RecordedResponse recordedResponse = i.next();
79        if (recordedResponse.request.url().equals(url)) {
80          i.remove();
81          return recordedResponse;
82        }
83      }
84
85      long nowMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
86      if (nowMillis >= timeoutMillis) break;
87      wait(timeoutMillis - nowMillis);
88    }
89
90    throw new AssertionError("Timed out waiting for response to " + url);
91  }
92
93  public synchronized void assertNoResponse(URL url) throws Exception {
94    for (RecordedResponse recordedResponse : responses) {
95      if (recordedResponse.request.url().equals(url)) {
96        throw new AssertionError("Expected no response for " + url);
97      }
98    }
99  }
100}
101