1 /*
2  * Copyright (C) 2016 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.internal.http;
17
18 import java.io.IOException;
19 import java.util.List;
20 import java.util.concurrent.TimeUnit;
21 import javax.annotation.Nullable;
22 import okhttp3.Call;
23 import okhttp3.Connection;
24 import okhttp3.Interceptor;
25 import okhttp3.Request;
26 import okhttp3.Response;
27 import okhttp3.internal.connection.Exchange;
28 import okhttp3.internal.connection.Transmitter;
29
30 import static okhttp3.internal.Util.checkDuration;
31
32 /**
33  * A concrete interceptor chain that carries the entire interceptor chain: all application
34  * interceptors, the OkHttp core, all network interceptors, and finally the network caller.
35  *
36  * <p>If the chain is for an application interceptor then {@link #connection} must be null.
37  * Otherwise it is for a network interceptor and {@link #connection} must be non-null.
38  */

39 public final class RealInterceptorChain implements Interceptor.Chain {
40   private final List<Interceptor> interceptors;
41   private final Transmitter transmitter;
42   private final @Nullable Exchange exchange;
43   private final int index;
44   private final Request request;
45   private final Call call;
46   private final int connectTimeout;
47   private final int readTimeout;
48   private final int writeTimeout;
49   private int calls;
50
51   public RealInterceptorChain(List<Interceptor> interceptors, Transmitter transmitter,
52       @Nullable Exchange exchange, int index, Request request, Call call,
53       int connectTimeout, int readTimeout, int writeTimeout) {
54     this.interceptors = interceptors;
55     this.transmitter = transmitter;
56     this.exchange = exchange;
57     this.index = index;
58     this.request = request;
59     this.call = call;
60     this.connectTimeout = connectTimeout;
61     this.readTimeout = readTimeout;
62     this.writeTimeout = writeTimeout;
63   }
64
65   @Override public @Nullable Connection connection() {
66     return exchange != null ? exchange.connection() : null;
67   }
68
69   @Override public int connectTimeoutMillis() {
70     return connectTimeout;
71   }
72
73   @Override public Interceptor.Chain withConnectTimeout(int timeout, TimeUnit unit) {
74     int millis = checkDuration("timeout", timeout, unit);
75     return new RealInterceptorChain(interceptors, transmitter, exchange, index, request, call,
76         millis, readTimeout, writeTimeout);
77   }
78
79   @Override public int readTimeoutMillis() {
80     return readTimeout;
81   }
82
83   @Override public Interceptor.Chain withReadTimeout(int timeout, TimeUnit unit) {
84     int millis = checkDuration("timeout", timeout, unit);
85     return new RealInterceptorChain(interceptors, transmitter, exchange, index, request, call,
86         connectTimeout, millis, writeTimeout);
87   }
88
89   @Override public int writeTimeoutMillis() {
90     return writeTimeout;
91   }
92
93   @Override public Interceptor.Chain withWriteTimeout(int timeout, TimeUnit unit) {
94     int millis = checkDuration("timeout", timeout, unit);
95     return new RealInterceptorChain(interceptors, transmitter, exchange, index, request, call,
96         connectTimeout, readTimeout, millis);
97   }
98
99   public Transmitter transmitter() {
100     return transmitter;
101   }
102
103   public Exchange exchange() {
104     if (exchange == nullthrow new IllegalStateException();
105     return exchange;
106   }
107
108   @Override public Call call() {
109     return call;
110   }
111
112   @Override public Request request() {
113     return request;
114   }
115
116   @Override public Response proceed(Request request) throws IOException {
117     return proceed(request, transmitter, exchange);
118   }
119
120   public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
121       throws IOException {
122     if (index >= interceptors.size()) throw new AssertionError();
123
124     calls++;
125
126     // If we already have a stream, confirm that the incoming request will use it.
127     if (this.exchange != null && !this.exchange.connection().supportsUrl(request.url())) {
128       throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
129           + " must retain the same host and port");
130     }
131
132     // If we already have a stream, confirm that this is the only call to chain.proceed().
133     if (this.exchange != null && calls > 1) {
134       throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
135           + " must call proceed() exactly once");
136     }
137
138     // Call the next interceptor in the chain.
139     RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
140         index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
141     Interceptor interceptor = interceptors.get(index);
142     Response response = interceptor.intercept(next);
143
144     // Confirm that the next interceptor made its required call to chain.proceed().
145     if (exchange != null && index + 1 < interceptors.size() && next.calls != 1) {
146       throw new IllegalStateException("network interceptor " + interceptor
147           + " must call proceed() exactly once");
148     }
149
150     // Confirm that the intercepted response isn't null.
151     if (response == null) {
152       throw new NullPointerException("interceptor " + interceptor + " returned null");
153     }
154
155     if (response.body() == null) {
156       throw new IllegalStateException(
157           "interceptor " + interceptor + " returned a response with no body");
158     }
159
160     return response;
161   }
162 }
163