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 "java.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 = CompletableFuture.failedFuture(new IOException("Too many retries")); 191 } else { 192 if (currentreq.timeval() != 0) { 193 // set timer 194 td = new TimedEvent(currentreq.timeval()); 195 client.registerTimer(td); 196 } 197 Exchange exch = getExchange(); 198 cf = requestFiltersAsync(currentreq) 199 .thenCompose(exch::responseAsync) 200 .thenCompose(this::responseFiltersAsync) 201 .thenCompose((Pair<HttpResponse,HttpRequestImpl> pair) -> { 202 HttpResponseImpl resp = (HttpResponseImpl)pair.first; 203 if (resp != null) { 204 if (attempts > 1) { 205 Log.logError("Succeeded on attempt: " + attempts); 206 } 207 return CompletableFuture.completedFuture(resp); 208 } else { 209 currentreq = pair.second; 210 Exchange previous = exch; 211 setExchange(new Exchange(currentreq, 212 currentreq.getAccessControlContext())); 213 //reads body off previous, and then waits for next response 214 return previous 215 .responseBodyAsync(HttpResponse.ignoreBody()) 216 .thenCompose(this::responseAsync); 217 } 218 }) 219 .handle((BiFunction<HttpResponse, Throwable, Pair<HttpResponse, Throwable>>) Pair::new) 220 .thenCompose((Pair<HttpResponse,Throwable> obj) -> { 221 HttpResponseImpl response = (HttpResponseImpl)obj.first; 222 if (response != null) { 223 return CompletableFuture.completedFuture(response); 224 } 225 // all exceptions thrown are handled here 226 CompletableFuture<HttpResponseImpl> error = getExceptionalCF(obj.second); 227 if (error == null) { 228 cancelTimer(); 229 return responseAsync(null); 230 } else { 231 return error; 232 } 233 }); 234 } 235 return cf; 236 } 237 238 /** 239 * Take a Throwable and return a suitable CompletableFuture that is 240 * completed exceptionally. 241 */ 242 private CompletableFuture<HttpResponseImpl> getExceptionalCF(Throwable t) { 243 if ((t instanceof CompletionException) || (t instanceof ExecutionException)) { 244 if (t.getCause() != null) { 245 t = t.getCause(); 246 } 247 } 248 if (cancelled && t instanceof IOException) { 249 t = new HttpTimeoutException("request timed out"); 250 } 251 return CompletableFuture.failedFuture(t); 252 } 253 254 <T> T responseBody(HttpResponse.BodyProcessor<T> processor) { 255 return getExchange().responseBody(processor); 256 } 257 258 <T> CompletableFuture<T> responseBodyAsync(HttpResponse.BodyProcessor<T> processor) { 259 return getExchange().responseBodyAsync(processor); 260 } 261 262 class TimedEvent extends TimeoutEvent { 263 TimedEvent(long timeval) { 264 super(timeval); 265 } 266 @Override 267 public void handle() { 268 cancel(); 269 } 270 271 } 272 }