1 /*
2  * Copyright (C) 2014 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  */

16 package okhttp3;
17
18 import java.io.IOException;
19 import java.io.InterruptedIOException;
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.concurrent.ExecutorService;
23 import java.util.concurrent.RejectedExecutionException;
24 import java.util.concurrent.atomic.AtomicInteger;
25 import okhttp3.internal.NamedRunnable;
26 import okhttp3.internal.cache.CacheInterceptor;
27 import okhttp3.internal.connection.ConnectInterceptor;
28 import okhttp3.internal.connection.Transmitter;
29 import okhttp3.internal.http.BridgeInterceptor;
30 import okhttp3.internal.http.CallServerInterceptor;
31 import okhttp3.internal.http.RealInterceptorChain;
32 import okhttp3.internal.http.RetryAndFollowUpInterceptor;
33 import okhttp3.internal.platform.Platform;
34 import okio.Timeout;
35
36 import static okhttp3.internal.Util.closeQuietly;
37 import static okhttp3.internal.platform.Platform.INFO;
38
39 final class RealCall implements Call {
40   final OkHttpClient client;
41
42   /**
43    * There is a cycle between the {@link Call} and {@link Transmitter} that makes this awkward.
44    * This is set after immediately after creating the call instance.
45    */

46   private Transmitter transmitter;
47
48   /** The application's original request unadulterated by redirects or auth headers. */
49   final Request originalRequest;
50   final boolean forWebSocket;
51
52   // Guarded by this.
53   private boolean executed;
54
55   private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
56     this.client = client;
57     this.originalRequest = originalRequest;
58     this.forWebSocket = forWebSocket;
59   }
60
61   static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
62     // Safely publish the Call instance to the EventListener.
63     RealCall call = new RealCall(client, originalRequest, forWebSocket);
64     call.transmitter = new Transmitter(client, call);
65     return call;
66   }
67
68   @Override public Request request() {
69     return originalRequest;
70   }
71
72   @Override public Response execute() throws IOException {
73     synchronized (this) {
74       if (executed) throw new IllegalStateException("Already Executed");
75       executed = true;
76     }
77     transmitter.timeoutEnter();
78     transmitter.callStart();
79     try {
80       client.dispatcher().executed(this);
81       return getResponseWithInterceptorChain();
82     } finally {
83       client.dispatcher().finished(this);
84     }
85   }
86
87   @Override public void enqueue(Callback responseCallback) {
88     synchronized (this) {
89       if (executed) throw new IllegalStateException("Already Executed");
90       executed = true;
91     }
92     transmitter.callStart();
93     client.dispatcher().enqueue(new AsyncCall(responseCallback));
94   }
95
96   @Override public void cancel() {
97     transmitter.cancel();
98   }
99
100   @Override public Timeout timeout() {
101     return transmitter.timeout();
102   }
103
104   @Override public synchronized boolean isExecuted() {
105     return executed;
106   }
107
108   @Override public boolean isCanceled() {
109     return transmitter.isCanceled();
110   }
111
112   @SuppressWarnings("CloneDoesntCallSuperClone"// We are a final type & this saves clearing state.
113   @Override public RealCall clone() {
114     return RealCall.newRealCall(client, originalRequest, forWebSocket);
115   }
116
117   final class AsyncCall extends NamedRunnable {
118     private final Callback responseCallback;
119     private volatile AtomicInteger callsPerHost = new AtomicInteger(0);
120
121     AsyncCall(Callback responseCallback) {
122       super("OkHttp %s", redactedUrl());
123       this.responseCallback = responseCallback;
124     }
125
126     AtomicInteger callsPerHost() {
127       return callsPerHost;
128     }
129
130     void reuseCallsPerHostFrom(AsyncCall other) {
131       this.callsPerHost = other.callsPerHost;
132     }
133
134     String host() {
135       return originalRequest.url().host();
136     }
137
138     Request request() {
139       return originalRequest;
140     }
141
142     RealCall get() {
143       return RealCall.this;
144     }
145
146     /**
147      * Attempt to enqueue this async call on {@code executorService}. This will attempt to clean up
148      * if the executor has been shut down by reporting the call as failed.
149      */

150     void executeOn(ExecutorService executorService) {
151       assert (!Thread.holdsLock(client.dispatcher()));
152       boolean success = false;
153       try {
154         executorService.execute(this);
155         success = true;
156       } catch (RejectedExecutionException e) {
157         InterruptedIOException ioException = new InterruptedIOException("executor rejected");
158         ioException.initCause(e);
159         transmitter.noMoreExchanges(ioException);
160         responseCallback.onFailure(RealCall.this, ioException);
161       } finally {
162         if (!success) {
163           client.dispatcher().finished(this); // This call is no longer running!
164         }
165       }
166     }
167
168     @Override protected void execute() {
169       boolean signalledCallback = false;
170       transmitter.timeoutEnter();
171       try {
172         Response response = getResponseWithInterceptorChain();
173         signalledCallback = true;
174         responseCallback.onResponse(RealCall.this, response);
175       } catch (IOException e) {
176         if (signalledCallback) {
177           // Do not signal the callback twice!
178           Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
179         } else {
180           responseCallback.onFailure(RealCall.this, e);
181         }
182       } catch (Throwable t) {
183         cancel();
184         if (!signalledCallback) {
185           IOException canceledException = new IOException("canceled due to " + t);
186           canceledException.addSuppressed(t);
187           responseCallback.onFailure(RealCall.this, canceledException);
188         }
189         throw t;
190       } finally {
191         client.dispatcher().finished(this);
192       }
193     }
194   }
195
196   /**
197    * Returns a string that describes this call. Doesn't include a full URL as that might contain
198    * sensitive information.
199    */

200   String toLoggableString() {
201     return (isCanceled() ? "canceled " : "")
202         + (forWebSocket ? "web socket" : "call")
203         + " to " + redactedUrl();
204   }
205
206   String redactedUrl() {
207     return originalRequest.url().redact();
208   }
209
210   Response getResponseWithInterceptorChain() throws IOException {
211     // Build a full stack of interceptors.
212     List<Interceptor> interceptors = new ArrayList<>();
213     interceptors.addAll(client.interceptors());
214     interceptors.add(new RetryAndFollowUpInterceptor(client));
215     interceptors.add(new BridgeInterceptor(client.cookieJar()));
216     interceptors.add(new CacheInterceptor(client.internalCache()));
217     interceptors.add(new ConnectInterceptor(client));
218     if (!forWebSocket) {
219       interceptors.addAll(client.networkInterceptors());
220     }
221     interceptors.add(new CallServerInterceptor(forWebSocket));
222
223     Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
224         originalRequest, this, client.connectTimeoutMillis(),
225         client.readTimeoutMillis(), client.writeTimeoutMillis());
226
227     boolean calledNoMoreExchanges = false;
228     try {
229       Response response = chain.proceed(originalRequest);
230       if (transmitter.isCanceled()) {
231         closeQuietly(response);
232         throw new IOException("Canceled");
233       }
234       return response;
235     } catch (IOException e) {
236       calledNoMoreExchanges = true;
237       throw transmitter.noMoreExchanges(e);
238     } finally {
239       if (!calledNoMoreExchanges) {
240         transmitter.noMoreExchanges(null);
241       }
242     }
243   }
244 }
245