1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.springframework.extensions.webscripts.connector;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.ByteArrayOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.io.UnsupportedEncodingException;
27 import java.net.ConnectException;
28 import java.net.MalformedURLException;
29 import java.net.SocketTimeoutException;
30 import java.net.URL;
31 import java.net.UnknownHostException;
32 import java.util.Enumeration;
33 import java.util.Map;
34
35 import javax.servlet.http.HttpServletRequest;
36 import javax.servlet.http.HttpServletResponse;
37
38 import org.apache.commons.httpclient.ConnectTimeoutException;
39 import org.apache.commons.httpclient.Header;
40 import org.apache.commons.httpclient.HttpClient;
41 import org.apache.commons.httpclient.methods.DeleteMethod;
42 import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
43 import org.apache.commons.httpclient.methods.GetMethod;
44 import org.apache.commons.httpclient.methods.HeadMethod;
45 import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
46 import org.apache.commons.httpclient.methods.PostMethod;
47 import org.apache.commons.httpclient.methods.PutMethod;
48 import org.apache.commons.httpclient.params.HttpClientParams;
49 import org.apache.commons.logging.Log;
50 import org.apache.commons.logging.LogFactory;
51 import org.springframework.extensions.surf.exception.WebScriptsPlatformException;
52 import org.springframework.extensions.surf.util.Base64;
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 public class RemoteClient extends AbstractClient
72 {
73 private static Log logger = LogFactory.getLog(RemoteClient.class);
74
75 private static final String CHARSETEQUALS = "charset=";
76 private static final int BUFFERSIZE = 4096;
77
78 private static final int CONNECT_TIMEOUT = 5000;
79 private static final int READ_TIMEOUT = 90000;
80
81 private Map<String, String> cookies;
82 private String defaultEncoding;
83 private String ticket;
84 private String ticketName = "alf_ticket";
85 private String requestContentType = "application/octet-stream";
86 private HttpMethod requestMethod = HttpMethod.GET;
87
88 private String username;
89 private String password;
90
91 private Map<String, String> requestProperties;
92
93
94 public static final int SC_REMOTE_CONN_TIMEOUT = 499;
95 public static final int SC_REMOTE_CONN_NOHOST = 498;
96
97
98 public static final int SC_MOVED_TEMPORARILY = 302;
99 public static final int SC_MOVED_PERMANENTLY = 301;
100 public static final int SC_SEE_OTHER = 303;
101 public static final int SC_TEMPORARY_REDIRECT = 307;
102
103 private static final int MAX_REDIRECTS = 10;
104
105
106
107
108
109
110
111
112 public RemoteClient(String endpoint)
113 {
114 this(endpoint, null);
115 }
116
117
118
119
120
121
122
123
124 public RemoteClient(String endpoint, String defaultEncoding)
125 {
126 super(endpoint);
127 this.defaultEncoding = defaultEncoding;
128 }
129
130
131
132
133
134
135 public void setTicket(String ticket)
136 {
137 this.ticket = ticket;
138 }
139
140
141
142
143
144
145 public String getTicket()
146 {
147 return this.ticket;
148 }
149
150
151
152
153
154
155
156
157
158 public void setTicketName(String ticketName)
159 {
160 this.ticketName = ticketName;
161 }
162
163
164
165
166 public String getTicketName()
167 {
168 return this.ticketName;
169 }
170
171
172
173
174
175
176
177 public void setUsernamePassword(String user, String pass)
178 {
179 this.username = user;
180 this.password = pass;
181 }
182
183
184
185
186
187 public void setRequestContentType(String contentType)
188 {
189 if (requestContentType != null && requestContentType.length() != 0)
190 {
191 this.requestContentType = contentType;
192 }
193 }
194
195
196
197
198
199
200
201 public void setRequestMethod(HttpMethod method)
202 {
203 if (method != null)
204 {
205 this.requestMethod = method;
206 }
207 }
208
209
210
211
212
213
214
215
216 public void setRequestProperties(Map<String, String> requestProperties)
217 {
218 this.requestProperties = requestProperties;
219 }
220
221
222
223
224
225
226
227 public void setCookies(Map<String, String> cookies)
228 {
229 this.cookies = cookies;
230 }
231
232
233
234
235
236
237
238 public Map<String, String> getCookies()
239 {
240 return this.cookies;
241 }
242
243
244
245
246
247
248
249
250
251
252
253 public Response call(String uri)
254 {
255 return call(uri, true, null);
256 }
257
258
259
260
261
262
263
264
265
266
267 public Response call(String uri, String body)
268 {
269 try
270 {
271 byte[] bytes = body.getBytes("UTF-8");
272 return call(uri, true, new ByteArrayInputStream(bytes));
273 }
274 catch (UnsupportedEncodingException e)
275 {
276 throw new WebScriptsPlatformException("Encoding not supported.", e);
277 }
278 }
279
280
281
282
283
284
285
286
287
288
289 public Response call(String uri, InputStream in)
290 {
291 return call(uri, true, in);
292 }
293
294
295
296
297
298
299
300
301
302
303
304
305 public Response call(String uri, boolean buildResponseString, InputStream in)
306 {
307 if (in != null)
308 {
309
310 if (this.requestMethod != HttpMethod.POST && this.requestMethod != HttpMethod.PUT)
311 {
312 this.requestMethod = HttpMethod.POST;
313 }
314 }
315
316 Response result;
317 ResponseStatus status = new ResponseStatus();
318 try
319 {
320 ByteArrayOutputStream bOut = new ByteArrayOutputStream(BUFFERSIZE);
321 String encoding = service(buildURL(uri), in, bOut, status);
322 if (buildResponseString)
323 {
324 String data;
325 if (encoding != null)
326 {
327 data = bOut.toString(encoding);
328 }
329 else
330 {
331 data = (defaultEncoding != null ? bOut.toString(defaultEncoding) : bOut.toString());
332 }
333 result = new Response(data, status);
334 }
335 else
336 {
337 result = new Response(new ByteArrayInputStream(bOut.toByteArray()), status);
338 }
339 result.setEncoding(encoding);
340 }
341 catch (IOException ioErr)
342 {
343 if (logger.isDebugEnabled())
344 logger.debug("Error status " + status.getCode() + " " + status.getMessage());
345
346
347 result = new Response(status);
348 }
349
350 return result;
351 }
352
353
354
355
356
357
358
359
360
361
362
363
364 public Response call(String uri, OutputStream out)
365 {
366 return call(uri, null, out);
367 }
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383 public Response call(String uri, InputStream in, OutputStream out)
384 {
385 if (in != null)
386 {
387
388 if (this.requestMethod != HttpMethod.POST && this.requestMethod != HttpMethod.PUT)
389 {
390 this.requestMethod = HttpMethod.POST;
391 }
392 }
393
394 Response result;
395 ResponseStatus status = new ResponseStatus();
396 try
397 {
398 String encoding = service(buildURL(uri), in, out, status);
399 result = new Response(status);
400 result.setEncoding(encoding);
401 }
402 catch (IOException ioErr)
403 {
404 if (logger.isDebugEnabled())
405 logger.debug("Error status " + status.getCode() + " " + status.getMessage());
406
407
408 result = new Response(status);
409 }
410
411 return result;
412 }
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430 public Response call(String uri, HttpServletRequest req, HttpServletResponse res)
431 {
432 Response result;
433 ResponseStatus status = new ResponseStatus();
434 try
435 {
436 boolean isPush = (requestMethod == HttpMethod.POST || requestMethod == HttpMethod.PUT);
437 String encoding = service(
438 buildURL(uri),
439 isPush ? req.getInputStream() : null,
440 res != null ? res.getOutputStream() : null,
441 req, res, status);
442 result = new Response(status);
443 result.setEncoding(encoding);
444 }
445 catch (IOException ioErr)
446 {
447 if (logger.isDebugEnabled())
448 logger.debug("Error status " + status.getCode() + " " + status.getMessage());
449
450
451 result = new Response(status);
452 }
453
454 return result;
455 }
456
457
458
459
460
461
462
463
464 protected URL processResponse(URL url, org.apache.commons.httpclient.HttpMethod method)
465 throws MalformedURLException
466 {
467 String redirectLocation = null;
468 for (Header header : method.getResponseHeaders())
469 {
470 String headerName = header.getName();
471 if (this.cookies != null && headerName.equalsIgnoreCase("set-cookie"))
472 {
473 String headerValue = header.getValue();
474
475 int z = headerValue.indexOf('=');
476 if (z != -1)
477 {
478 String cookieName = headerValue.substring(0, z);
479 String cookieValue = headerValue.substring(z + 1, headerValue.length());
480 int y = cookieValue.indexOf(';');
481 if (y != -1)
482 {
483 cookieValue = cookieValue.substring(0, y);
484 }
485
486
487 if (logger.isDebugEnabled())
488 logger.debug("RemoteClient found set-cookie: " + cookieName + " = " + cookieValue);
489
490 this.cookies.put(cookieName, cookieValue);
491 }
492 }
493 if (headerName.equalsIgnoreCase("Location"))
494 {
495 switch (method.getStatusCode())
496 {
497 case RemoteClient.SC_MOVED_TEMPORARILY:
498 case RemoteClient.SC_MOVED_PERMANENTLY:
499 case RemoteClient.SC_SEE_OTHER:
500 case RemoteClient.SC_TEMPORARY_REDIRECT:
501 redirectLocation = header.getValue();
502 }
503 }
504 }
505 return redirectLocation == null ? null : new URL(url, redirectLocation);
506 }
507
508
509
510
511
512
513
514
515
516
517
518 private URL buildURL(String uri) throws MalformedURLException
519 {
520 URL url;
521
522 String resolvedUri = uri.startsWith(endpoint) ? uri : endpoint + uri;
523 if (getTicket() == null)
524 {
525 url = new URL(resolvedUri);
526 }
527 else
528 {
529 url = new URL(resolvedUri +
530 (uri.lastIndexOf('?') == -1 ? ("?"+getTicketName()+"="+getTicket()) : ("&"+getTicketName()+"="+getTicket())));
531 }
532 return url;
533 }
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549 private String service(URL url, InputStream in, OutputStream out, ResponseStatus status)
550 throws IOException
551 {
552 return service(url, in, out, null, null, status);
553 }
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570 private String service(URL url, InputStream in, OutputStream out,
571 HttpServletRequest req, HttpServletResponse res, ResponseStatus status)
572 throws IOException
573 {
574 final boolean trace = logger.isTraceEnabled();
575 final boolean debug = logger.isDebugEnabled();
576 if (debug)
577 {
578 logger.debug("Executing " + "(" + requestMethod + ") " + url.toString());
579 if (in != null) logger.debug(" - InputStream supplied - will push...");
580 if (out != null) logger.debug(" - OutputStream supplied - will stream response...");
581 if (req != null && res != null) logger.debug(" - Full Proxy mode between servlet request and response...");
582 }
583
584 HttpClient client = new HttpClient();
585
586 final HttpClientParams params = client.getParams();
587 params.setBooleanParameter("http.connection.stalecheck", false);
588 params.setBooleanParameter("http.tcp.nodelay", true);
589 if (!debug)
590 {
591 params.setIntParameter("http.connection.timeout", CONNECT_TIMEOUT);
592 params.setIntParameter("http.socket.timeout", READ_TIMEOUT);
593 }
594
595 URL redirectURL = url;
596 int responseCode;
597 org.apache.commons.httpclient.HttpMethod method = null;
598 int retries = 0;
599
600 int maxRetries = in == null ? MAX_REDIRECTS : 1;
601 try
602 {
603 do
604 {
605
606 if (method != null)
607 {
608 method.releaseConnection();
609 method = null;
610 }
611
612 switch (this.requestMethod)
613 {
614 default:
615 case GET:
616 method = new GetMethod(redirectURL.toString());
617 break;
618 case PUT:
619 method = new PutMethod(redirectURL.toString());
620 break;
621 case POST:
622 method = new PostMethod(redirectURL.toString());
623 break;
624 case DELETE:
625 method = new DeleteMethod(redirectURL.toString());
626 break;
627 case HEAD:
628 method = new HeadMethod(redirectURL.toString());
629 break;
630 }
631
632
633 method.setFollowRedirects(false);
634
635
636 if (req != null)
637 {
638 Enumeration<String> headers = req.getHeaderNames();
639 while (headers.hasMoreElements())
640 {
641 String key = headers.nextElement();
642 if (key != null)
643 {
644 method.setRequestHeader(key, req.getHeader(key));
645 if (trace)
646 logger.trace("Proxy request header: " + key + "=" + req.getHeader(key));
647 }
648 }
649 }
650
651
652 if (this.requestProperties != null && this.requestProperties.size() != 0)
653 {
654 for (Map.Entry<String, String> entry : requestProperties.entrySet())
655 {
656 String headerName = entry.getKey();
657 String headerValue = this.requestProperties.get(headerName);
658 method.setRequestHeader(headerName, headerValue);
659 if (trace)
660 logger.trace("Set request header: " + headerName + "=" + headerValue);
661 }
662 }
663
664
665 if (this.cookies != null && !this.cookies.isEmpty())
666 {
667 StringBuilder builder = new StringBuilder(128);
668 for (Map.Entry<String, String> entry : this.cookies.entrySet())
669 {
670 if (builder.length() != 0)
671 {
672 builder.append(';');
673 }
674 builder.append(entry.getKey());
675 builder.append('=');
676 builder.append(entry.getValue());
677 }
678
679 String cookieString = builder.toString();
680
681 if (debug)
682 logger.debug("Setting cookie header: " + cookieString);
683 method.setRequestHeader("Cookie", cookieString);
684 }
685
686
687 if (this.username != null && this.password != null)
688 {
689 String auth = this.username + ':' + this.password;
690 method.addRequestHeader("Authorization", "Basic " + Base64.encodeBytes(auth.getBytes()));
691 if (debug)
692 logger.debug("Applied HTTP Basic Authorization");
693 }
694
695
696 if (in != null)
697 {
698 method.setRequestHeader("Content-Type", this.requestContentType);
699
700
701
702 int contentLength = InputStreamRequestEntity.CONTENT_LENGTH_AUTO;
703 if (req != null)
704 {
705 contentLength = req.getContentLength();
706 }
707
708 if (debug)
709 logger.debug("Setting content-type=" + this.requestContentType + " content-length=" + contentLength);
710
711 ((EntityEnclosingMethod) method).setRequestEntity(new InputStreamRequestEntity(in, contentLength));
712
713
714 if (req != null && contentLength == 0 && method instanceof PostMethod)
715 {
716 Map<String, String[]> postParams = req.getParameterMap();
717
718 if (postParams != null)
719 {
720 for (String key : postParams.keySet())
721 {
722 String[] values = postParams.get(key);
723 for (int i = 0; i < values.length; i++)
724 {
725 ((PostMethod) method).addParameter(key, values[i]);
726 }
727 }
728 }
729 }
730 }
731
732
733 responseCode = client.executeMethod(method);
734 redirectURL = processResponse(redirectURL, method);
735 }
736 while (redirectURL != null && ++retries < maxRetries);
737
738
739 if (res != null)
740 {
741 res.setStatus(responseCode);
742 }
743 status.setCode(responseCode);
744 if (debug) logger.debug("Response status code: " + responseCode);
745
746
747
748
749 Header contentType = null;
750 Header contentLength = null;
751 for (Header header : method.getResponseHeaders())
752 {
753
754
755
756
757
758 final String key = header.getName();
759 if (key != null)
760 {
761 if (!key.equalsIgnoreCase("Server") && !key.equalsIgnoreCase("Transfer-Encoding"))
762 {
763 if (res != null)
764 {
765 res.setHeader(key, header.getValue());
766 }
767
768
769 status.setHeader(key, header.getValue());
770
771 if (trace) logger.trace("Response header: " + key + "=" + header.getValue());
772 }
773
774
775 if (contentType == null && key.equalsIgnoreCase("Content-Type"))
776 {
777 contentType = header;
778 }
779
780 else if (contentLength == null && key.equalsIgnoreCase("Content-Length"))
781 {
782 contentLength = header;
783 }
784 }
785 }
786
787
788 String encoding = null;
789 String ct = null;
790 if (contentType != null)
791 {
792 ct = contentType.getValue();
793 int csi = ct.indexOf(CHARSETEQUALS);
794 if (csi != -1)
795 {
796 encoding = ct.substring(csi + CHARSETEQUALS.length());
797 }
798 }
799 if (debug) logger.debug("Response encoding: " + contentType);
800
801
802 int bufferSize = BUFFERSIZE;
803 if (contentLength != null)
804 {
805 int length = Integer.parseInt(contentLength.getValue());
806 if (length < bufferSize)
807 {
808 bufferSize = length;
809 }
810 }
811 StringBuilder traceBuf = null;
812 if (trace)
813 {
814 traceBuf = new StringBuilder(bufferSize);
815 }
816 boolean responseCommit = false;
817 if (responseCode != HttpServletResponse.SC_NOT_MODIFIED)
818 {
819 InputStream input = method.getResponseBodyAsStream();
820 if (input != null)
821 {
822 try
823 {
824 byte[] buffer = new byte[bufferSize];
825 int read = input.read(buffer);
826 if (read != -1) responseCommit = true;
827 while (read != -1)
828 {
829 if (out != null)
830 {
831 out.write(buffer, 0, read);
832 }
833
834 if (trace)
835 {
836 if (ct != null && (ct.startsWith("text/") || ct.startsWith("application/json")))
837 {
838 traceBuf.append(new String(buffer, 0, read));
839 }
840 }
841
842 read = input.read(buffer);
843 }
844 }
845 finally
846 {
847 if (trace && traceBuf.length() != 0)
848 {
849 logger.trace("Output (" + (traceBuf.length()) + " bytes) from: " + url.toString());
850 logger.trace(traceBuf.toString());
851 }
852 try
853 {
854 try
855 {
856 input.close();
857 }
858 finally
859 {
860 if (responseCommit)
861 {
862 if (out != null)
863 {
864 out.close();
865 }
866 }
867 }
868 }
869 catch (IOException e)
870 {
871 if (logger.isWarnEnabled())
872 logger.warn("Exception during close() of HTTP API connection", e);
873 }
874 }
875 }
876 }
877
878
879 if (res != null && responseCode != HttpServletResponse.SC_OK && !responseCommit)
880 {
881 res.sendError(responseCode, method.getStatusText());
882 }
883
884
885 return encoding;
886 }
887 catch (ConnectTimeoutException timeErr)
888 {
889
890 status.setCode(SC_REMOTE_CONN_TIMEOUT);
891 status.setException(timeErr);
892 status.setMessage(timeErr.getMessage());
893 if (res != null)
894 {
895
896 res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, timeErr.getMessage());
897 }
898
899 throw timeErr;
900 }
901 catch (SocketTimeoutException socketErr)
902 {
903
904 status.setCode(SC_REMOTE_CONN_TIMEOUT);
905 status.setException(socketErr);
906 status.setMessage(socketErr.getMessage());
907 if (res != null)
908 {
909
910 res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, socketErr.getMessage());
911 }
912
913 throw socketErr;
914 }
915 catch (UnknownHostException hostErr)
916 {
917
918 status.setCode(SC_REMOTE_CONN_NOHOST);
919 status.setException(hostErr);
920 status.setMessage(hostErr.getMessage());
921 if (res != null)
922 {
923
924 res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, hostErr.getMessage());
925 }
926
927 throw hostErr;
928 }
929 catch (ConnectException connErr)
930 {
931
932 status.setCode(SC_REMOTE_CONN_NOHOST);
933 status.setException(connErr);
934 status.setMessage(connErr.getMessage());
935 if (res != null)
936 {
937
938 res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, connErr.getMessage());
939 }
940
941 throw connErr;
942 }
943 catch (IOException ioErr)
944 {
945
946 status.setCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
947 status.setException(ioErr);
948 status.setMessage(ioErr.getMessage());
949 if (res != null)
950 {
951 res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ioErr.getMessage());
952 }
953
954 throw ioErr;
955 }
956 finally
957 {
958
959 method.releaseConnection();
960 this.requestContentType = "application/octet-stream";
961 this.requestMethod = HttpMethod.GET;
962 }
963 }
964 }