1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.client;
20
21 import java.net.URI;
22 import java.net.URISyntaxException;
23 import java.util.List;
24 import java.util.concurrent.CountDownLatch;
25 import java.util.concurrent.ExecutionException;
26 import java.util.concurrent.atomic.AtomicReference;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29
30 import org.eclipse.jetty.client.api.Request;
31 import org.eclipse.jetty.client.api.Response;
32 import org.eclipse.jetty.client.api.Result;
33 import org.eclipse.jetty.client.util.BufferingResponseListener;
34 import org.eclipse.jetty.http.HttpMethod;
35 import org.eclipse.jetty.util.log.Log;
36 import org.eclipse.jetty.util.log.Logger;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 public class HttpRedirector
61 {
62 private static final Logger LOG = Log.getLogger(HttpRedirector.class);
63 private static final String SCHEME_REGEXP = "(^https?)";
64 private static final String AUTHORITY_REGEXP = "([^/\\?#]+)";
65
66 private static final String DESTINATION_REGEXP = "(" + SCHEME_REGEXP + "://" + AUTHORITY_REGEXP + ")?";
67 private static final String PATH_REGEXP = "([^\\?#]*)";
68 private static final String QUERY_REGEXP = "([^#]*)";
69 private static final String FRAGMENT_REGEXP = "(.*)";
70 private static final Pattern URI_PATTERN = Pattern.compile(DESTINATION_REGEXP + PATH_REGEXP + QUERY_REGEXP + FRAGMENT_REGEXP);
71 private static final String ATTRIBUTE = HttpRedirector.class.getName() + ".redirects";
72
73 private final HttpClient client;
74 private final ResponseNotifier notifier;
75
76 public HttpRedirector(HttpClient client)
77 {
78 this.client = client;
79 this.notifier = new ResponseNotifier(client);
80 }
81
82
83
84
85
86 public boolean isRedirect(Response response)
87 {
88 switch (response.getStatus())
89 {
90 case 301:
91 case 302:
92 case 303:
93 case 307:
94 return true;
95 default:
96 return false;
97 }
98 }
99
100
101
102
103
104
105
106
107
108
109
110 public Result redirect(Request request, Response response) throws InterruptedException, ExecutionException
111 {
112 final AtomicReference<Result> resultRef = new AtomicReference<>();
113 final CountDownLatch latch = new CountDownLatch(1);
114 Request redirect = redirect(request, response, new BufferingResponseListener()
115 {
116 @Override
117 public void onComplete(Result result)
118 {
119 resultRef.set(new Result(result.getRequest(),
120 result.getRequestFailure(),
121 new HttpContentResponse(result.getResponse(), getContent(), getEncoding()),
122 result.getResponseFailure()));
123 latch.countDown();
124 }
125 });
126
127 try
128 {
129 latch.await();
130 Result result = resultRef.get();
131 if (result.isFailed())
132 throw new ExecutionException(result.getFailure());
133 return result;
134 }
135 catch (InterruptedException x)
136 {
137
138 redirect.abort(x);
139 throw x;
140 }
141 }
142
143
144
145
146
147
148
149
150
151 public Request redirect(Request request, Response response, Response.CompleteListener listener)
152 {
153 if (isRedirect(response))
154 {
155 String location = response.getHeaders().get("Location");
156 URI newURI = extractRedirectURI(response);
157 if (newURI != null)
158 {
159 LOG.debug("Redirecting to {} (Location: {})", newURI, location);
160 return redirect(request, response, listener, newURI);
161 }
162 else
163 {
164 fail(request, response, new HttpResponseException("Invalid 'Location' header: " + location, response));
165 return null;
166 }
167 }
168 else
169 {
170 fail(request, response, new HttpResponseException("Cannot redirect: " + response, response));
171 return null;
172 }
173 }
174
175
176
177
178
179
180
181
182 public URI extractRedirectURI(Response response)
183 {
184 String location = response.getHeaders().get("location");
185 if (location != null)
186 return sanitize(location);
187 return null;
188 }
189
190 private URI sanitize(String location)
191 {
192
193
194
195 try
196 {
197
198 return new URI(location);
199 }
200 catch (URISyntaxException x)
201 {
202 Matcher matcher = URI_PATTERN.matcher(location);
203 if (matcher.matches())
204 {
205 String scheme = matcher.group(2);
206 String authority = matcher.group(3);
207 String path = matcher.group(4);
208 String query = matcher.group(5);
209 if (query.length() == 0)
210 query = null;
211 String fragment = matcher.group(6);
212 if (fragment.length() == 0)
213 fragment = null;
214 try
215 {
216 return new URI(scheme, authority, path, query, fragment);
217 }
218 catch (URISyntaxException xx)
219 {
220
221 }
222 }
223 return null;
224 }
225 }
226
227 private Request redirect(Request request, Response response, Response.CompleteListener listener, URI newURI)
228 {
229 if (!newURI.isAbsolute())
230 newURI = request.getURI().resolve(newURI);
231
232 int status = response.getStatus();
233 switch (status)
234 {
235 case 301:
236 {
237 String method = request.getMethod();
238 if (HttpMethod.GET.is(method) || HttpMethod.HEAD.is(method) || HttpMethod.PUT.is(method))
239 return redirect(request, response, listener, newURI, method);
240 else if (HttpMethod.POST.is(method))
241 return redirect(request, response, listener, newURI, HttpMethod.GET.asString());
242 fail(request, response, new HttpResponseException("HTTP protocol violation: received 301 for non GET/HEAD/POST/PUT request", response));
243 return null;
244 }
245 case 302:
246 {
247 String method = request.getMethod();
248 if (HttpMethod.HEAD.is(method) || HttpMethod.PUT.is(method))
249 return redirect(request, response, listener, newURI, method);
250 else
251 return redirect(request, response, listener, newURI, HttpMethod.GET.asString());
252 }
253 case 303:
254 {
255 String method = request.getMethod();
256 if (HttpMethod.HEAD.is(method))
257 return redirect(request, response, listener, newURI, method);
258 else
259 return redirect(request, response, listener, newURI, HttpMethod.GET.asString());
260 }
261 case 307:
262 {
263
264 return redirect(request, response, listener, newURI, request.getMethod());
265 }
266 default:
267 {
268 fail(request, response, new HttpResponseException("Unhandled HTTP status code " + status, response));
269 return null;
270 }
271 }
272 }
273
274 private Request redirect(final Request request, Response response, Response.CompleteListener listener, URI location, String method)
275 {
276 HttpConversation conversation = client.getConversation(request.getConversationID(), false);
277 Integer redirects = conversation == null ? Integer.valueOf(0) : (Integer)conversation.getAttribute(ATTRIBUTE);
278 if (redirects == null)
279 redirects = 0;
280 if (redirects < client.getMaxRedirects())
281 {
282 ++redirects;
283 if (conversation != null)
284 conversation.setAttribute(ATTRIBUTE, redirects);
285
286 Request redirect = client.copyRequest(request, location);
287
288
289 redirect.method(method);
290
291 redirect.onRequestBegin(new Request.BeginListener()
292 {
293 @Override
294 public void onBegin(Request redirect)
295 {
296 Throwable cause = request.getAbortCause();
297 if (cause != null)
298 redirect.abort(cause);
299 }
300 });
301
302 redirect.send(listener);
303 return redirect;
304 }
305 else
306 {
307 fail(request, response, new HttpResponseException("Max redirects exceeded " + redirects, response));
308 return null;
309 }
310 }
311
312 protected void fail(Request request, Response response, Throwable failure)
313 {
314 HttpConversation conversation = client.getConversation(request.getConversationID(), false);
315 conversation.updateResponseListeners(null);
316 List<Response.ResponseListener> listeners = conversation.getResponseListeners();
317 notifier.notifyFailure(listeners, response, failure);
318 notifier.notifyComplete(listeners, new Result(request, response, failure));
319 }
320 }