< prev index next >
1 /*
2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 */
24
25 package java.net.http;
26
27 import java.io.IOException;
28 import java.util.List;
29 import java.util.concurrent.CompletableFuture;
30 import java.util.concurrent.CompletionException;
31 import java.util.concurrent.ExecutionException;
32 import java.util.function.BiFunction;
33
34 import static java.net.http.Pair.pair;
35
36 /**
37 * Encapsulates multiple Exchanges belonging to one HttpRequestImpl.
38 * - manages filters
39 * - retries due to filters.
40 * - I/O errors and most other exceptions get returned directly to user
41 *
42 * Creates a new Exchange for each request/response interaction
43 */
44 class MultiExchange {
45
46 final HttpRequestImpl request; // the user request
47 final HttpClientImpl client;
48 HttpRequestImpl currentreq; // used for async only
49 Exchange exchange; // the current exchange
50 Exchange previous;
51 int attempts;
52 // Maximum number of times a request will be retried/redirected
53 // for any reason
54
55 final static int DEFAULT_MAX_ATTEMPTS = 5;
56 final static int max_attempts = Utils.getIntegerNetProperty(
57 "sun.net.httpclient.redirects.retrylimit", DEFAULT_MAX_ATTEMPTS
58 );
59
60 private final List<HeaderFilter> filters;
61 TimedEvent td;
62 boolean cancelled = false;
63
64 /**
65 * Filter fields. These are attached as required by filters
66 * and only used by the filter implementations. This could be
67 * generalised into Objects that are passed explicitly to the filters
68 * (one per MultiExchange object, and one per Exchange object possibly)
69 */
70 volatile AuthenticationFilter.AuthInfo serverauth, proxyauth;
71 // RedirectHandler
72 volatile int numberOfRedirects = 0;
73
74 /**
75 */
76 MultiExchange(HttpRequestImpl request) {
77 this.exchange = new Exchange(request);
78 this.previous = null;
79 this.request = request;
80 this.currentreq = request;
81 this.attempts = 0;
82 this.client = request.client();
83 this.filters = client.filterChain();
84 }
85
86 public HttpResponseImpl response() throws IOException, InterruptedException {
87 HttpRequestImpl r = request;
88 if (r.timeval() != 0) {
89 // set timer
90 td = new TimedEvent(r.timeval());
91 client.registerTimer(td);
92 }
93 while (attempts < max_attempts) {
94 try {
95 attempts++;
96 Exchange currExchange = getExchange();
97 requestFilters(r);
98 HttpResponseImpl response = currExchange.response();
99 Pair<HttpResponse, HttpRequestImpl> filterResult = responseFilters(response);
100 HttpRequestImpl newreq = filterResult.second;
101 if (newreq == null) {
102 if (attempts > 1) {
103 Log.logError("Succeeded on attempt: " + attempts);
104 }
105 cancelTimer();
106 return response;
107 }
108 response.body(HttpResponse.ignoreBody());
109 setExchange(new Exchange(newreq, currExchange.getAccessControlContext() ));
110 r = newreq;
111 } catch (IOException e) {
112 if (cancelled) {
113 throw new HttpTimeoutException("Request timed out");
114 }
115 throw e;
116 }
117 }
118 cancelTimer();
119 throw new IOException("Retry limit exceeded");
120 }
121
122 private synchronized Exchange getExchange() {
123 return exchange;
124 }
125
126 private synchronized void setExchange(Exchange exchange) {
127 this.exchange = exchange;
128 }
129
130 private void cancelTimer() {
131 if (td != null) {
132 client.cancelTimer(td);
133 }
134 }
135
136 private void requestFilters(HttpRequestImpl r) throws IOException {
137 for (HeaderFilter filter : filters) {
138 filter.request(r);
139 }
140 }
141
142 // Filters are assumed to be non-blocking so the async
143 // versions of these methods just call the blocking ones
144
145 private CompletableFuture<Void> requestFiltersAsync(HttpRequestImpl r) {
146 CompletableFuture<Void> cf = new CompletableFuture<>();
147 try {
148 requestFilters(r);
149 cf.complete(null);
150 } catch(Throwable e) {
151 cf.completeExceptionally(e);
152 }
153 return cf;
154 }
155
156
157 private Pair<HttpResponse,HttpRequestImpl>
158 responseFilters(HttpResponse response) throws IOException
159 {
160 for (HeaderFilter filter : filters) {
161 HttpRequestImpl newreq = filter.response((HttpResponseImpl)response);
162 if (newreq != null) {
163 return pair(null, newreq);
164 }
165 }
166 return pair(response, null);
167 }
168
169 private CompletableFuture<Pair<HttpResponse,HttpRequestImpl>>
170 responseFiltersAsync(HttpResponse response)
171 {
172 CompletableFuture<Pair<HttpResponse,HttpRequestImpl>> cf = new CompletableFuture<>();
173 try {
174 Pair<HttpResponse,HttpRequestImpl> n = responseFilters(response); // assumed to be fast
175 cf.complete(n);
176 } catch (Throwable e) {
177 cf.completeExceptionally(e);
178 }
179 return cf;
180 }
181
182 public void cancel() {
183 cancelled = true;
184 getExchange().cancel();
185 }
186
187 public CompletableFuture<HttpResponseImpl> responseAsync(Void v) {
188 CompletableFuture<HttpResponseImpl> cf;
189 if (++attempts > max_attempts) {
190 cf = new CompletableFuture<>();
191 cf.completeExceptionally(new IOException("Too many retries"));
192 } else {
193 if (currentreq.timeval() != 0) {
194 // set timer
195 td = new TimedEvent(currentreq.timeval());
196 client.registerTimer(td);
197 }
198 Exchange exch = getExchange();
199 cf = requestFiltersAsync(currentreq)
200 .thenCompose(exch::responseAsync)
201 .thenCompose(this::responseFiltersAsync)
202 .thenCompose((Pair<HttpResponse,HttpRequestImpl> pair) -> {
203 HttpResponseImpl resp = (HttpResponseImpl)pair.first;
204 if (resp != null) {
205 if (attempts > 1) {
206 Log.logError("Succeeded on attempt: " + attempts);
207 }
208 return CompletableFuture.completedFuture(resp);
209 } else {
210 currentreq = pair.second;
211 Exchange previous = exch;
212 setExchange(new Exchange(currentreq,
213 currentreq.getAccessControlContext()));
214 //reads body off previous, and then waits for next response
215 return previous
216 .responseBodyAsync(HttpResponse.ignoreBody())
217 .thenCompose(this::responseAsync);
218 }
219 })
220 .handle((BiFunction<HttpResponse, Throwable, Pair<HttpResponse, Throwable>>) Pair::new)
221 .thenCompose((Pair<HttpResponse,Throwable> obj) -> {
222 HttpResponseImpl response = (HttpResponseImpl)obj.first;
223 if (response != null) {
224 return CompletableFuture.completedFuture(response);
225 }
226 // all exceptions thrown are handled here
227 CompletableFuture<HttpResponseImpl> error = getExceptionalCF(obj.second);
228 if (error == null) {
229 cancelTimer();
230 return responseAsync(null);
231 } else {
232 return error;
233 }
234 });
235 }
236 return cf;
237 }
238
239 /**
240 * Take a Throwable and return a suitable CompletableFuture that is
241 * completed exceptionally.
242 */
243 private CompletableFuture<HttpResponseImpl> getExceptionalCF(Throwable t) {
244 CompletableFuture<HttpResponseImpl> error = new CompletableFuture<>();
245 if ((t instanceof CompletionException) || (t instanceof ExecutionException)) {
246 if (t.getCause() != null) {
247 t = t.getCause();
248 }
249 }
250 if (cancelled && t instanceof IOException) {
251 t = new HttpTimeoutException("request timed out");
252 }
253 error.completeExceptionally(t);
254 return error;
255 }
256
257 <T> T responseBody(HttpResponse.BodyProcessor<T> processor) {
258 return getExchange().responseBody(processor);
259 }
260
261 <T> CompletableFuture<T> responseBodyAsync(HttpResponse.BodyProcessor<T> processor) {
262 return getExchange().responseBodyAsync(processor);
263 }
264
265 class TimedEvent extends TimeoutEvent {
266 TimedEvent(long timeval) {
267 super(timeval);
268 }
269 @Override
270 public void handle() {
271 cancel();
272 }
273
274 }
275 }
< prev index next >