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.io.IOException;
22 import java.net.CookieManager;
23 import java.net.CookiePolicy;
24 import java.net.CookieStore;
25 import java.net.SocketAddress;
26 import java.net.URI;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.Objects;
36 import java.util.Set;
37 import java.util.concurrent.ConcurrentHashMap;
38 import java.util.concurrent.ConcurrentMap;
39 import java.util.concurrent.ExecutionException;
40 import java.util.concurrent.Executor;
41 import java.util.concurrent.TimeUnit;
42 import java.util.concurrent.TimeoutException;
43
44 import org.eclipse.jetty.client.api.AuthenticationStore;
45 import org.eclipse.jetty.client.api.Connection;
46 import org.eclipse.jetty.client.api.ContentResponse;
47 import org.eclipse.jetty.client.api.Destination;
48 import org.eclipse.jetty.client.api.Request;
49 import org.eclipse.jetty.client.api.Response;
50 import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
51 import org.eclipse.jetty.client.util.FormContentProvider;
52 import org.eclipse.jetty.http.HttpField;
53 import org.eclipse.jetty.http.HttpHeader;
54 import org.eclipse.jetty.http.HttpMethod;
55 import org.eclipse.jetty.http.HttpScheme;
56 import org.eclipse.jetty.io.ByteBufferPool;
57 import org.eclipse.jetty.io.MappedByteBufferPool;
58 import org.eclipse.jetty.util.Fields;
59 import org.eclipse.jetty.util.Jetty;
60 import org.eclipse.jetty.util.Promise;
61 import org.eclipse.jetty.util.SocketAddressResolver;
62 import org.eclipse.jetty.util.component.ContainerLifeCycle;
63 import org.eclipse.jetty.util.log.Log;
64 import org.eclipse.jetty.util.log.Logger;
65 import org.eclipse.jetty.util.ssl.SslContextFactory;
66 import org.eclipse.jetty.util.thread.QueuedThreadPool;
67 import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
68 import org.eclipse.jetty.util.thread.Scheduler;
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 public class HttpClient extends ContainerLifeCycle
105 {
106 private static final Logger LOG = Log.getLogger(HttpClient.class);
107
108 private final ConcurrentMap<Origin, HttpDestination> destinations = new ConcurrentHashMap<>();
109 private final List<ProtocolHandler> handlers = new ArrayList<>();
110 private final List<Request.Listener> requestListeners = new ArrayList<>();
111 private final AuthenticationStore authenticationStore = new HttpAuthenticationStore();
112 private final Set<ContentDecoder.Factory> decoderFactories = new ContentDecoderFactorySet();
113 private final ProxyConfiguration proxyConfig = new ProxyConfiguration();
114 private final HttpClientTransport transport;
115 private final SslContextFactory sslContextFactory;
116 private volatile CookieManager cookieManager;
117 private volatile CookieStore cookieStore;
118 private volatile Executor executor;
119 private volatile ByteBufferPool byteBufferPool;
120 private volatile Scheduler scheduler;
121 private volatile SocketAddressResolver resolver;
122 private volatile HttpField agentField = new HttpField(HttpHeader.USER_AGENT, "Jetty/" + Jetty.VERSION);
123 private volatile boolean followRedirects = true;
124 private volatile int maxConnectionsPerDestination = 64;
125 private volatile int maxRequestsQueuedPerDestination = 1024;
126 private volatile int requestBufferSize = 4096;
127 private volatile int responseBufferSize = 16384;
128 private volatile int maxRedirects = 8;
129 private volatile SocketAddress bindAddress;
130 private volatile long connectTimeout = 15000;
131 private volatile long addressResolutionTimeout = 15000;
132 private volatile long idleTimeout;
133 private volatile boolean tcpNoDelay = true;
134 private volatile boolean dispatchIO = true;
135 private volatile boolean strictEventOrdering = false;
136 private volatile HttpField encodingField;
137 private volatile boolean removeIdleDestinations = false;
138
139
140
141
142
143
144
145 public HttpClient()
146 {
147 this(null);
148 }
149
150
151
152
153
154
155
156
157 public HttpClient(SslContextFactory sslContextFactory)
158 {
159 this(new HttpClientTransportOverHTTP(), sslContextFactory);
160 }
161
162 public HttpClient(HttpClientTransport transport, SslContextFactory sslContextFactory)
163 {
164 this.transport = transport;
165 this.sslContextFactory = sslContextFactory;
166 }
167
168 public HttpClientTransport getTransport()
169 {
170 return transport;
171 }
172
173
174
175
176
177 public SslContextFactory getSslContextFactory()
178 {
179 return sslContextFactory;
180 }
181
182 @Override
183 protected void doStart() throws Exception
184 {
185 if (sslContextFactory != null)
186 addBean(sslContextFactory);
187
188 String name = HttpClient.class.getSimpleName() + "@" + hashCode();
189
190 if (executor == null)
191 {
192 QueuedThreadPool threadPool = new QueuedThreadPool();
193 threadPool.setName(name);
194 executor = threadPool;
195 }
196 addBean(executor);
197
198 if (byteBufferPool == null)
199 byteBufferPool = new MappedByteBufferPool();
200 addBean(byteBufferPool);
201
202 if (scheduler == null)
203 scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false);
204 addBean(scheduler);
205
206 transport.setHttpClient(this);
207 addBean(transport);
208
209 resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout());
210
211 handlers.add(new ContinueProtocolHandler(this));
212 handlers.add(new RedirectProtocolHandler(this));
213 handlers.add(new WWWAuthenticationProtocolHandler(this));
214 handlers.add(new ProxyAuthenticationProtocolHandler(this));
215
216 decoderFactories.add(new GZIPContentDecoder.Factory());
217
218 cookieManager = newCookieManager();
219 cookieStore = cookieManager.getCookieStore();
220
221 super.doStart();
222 }
223
224 private CookieManager newCookieManager()
225 {
226 return new CookieManager(getCookieStore(), CookiePolicy.ACCEPT_ALL);
227 }
228
229 @Override
230 protected void doStop() throws Exception
231 {
232 cookieStore.removeAll();
233 decoderFactories.clear();
234 handlers.clear();
235
236 for (HttpDestination destination : destinations.values())
237 destination.close();
238 destinations.clear();
239
240 requestListeners.clear();
241 authenticationStore.clearAuthentications();
242 authenticationStore.clearAuthenticationResults();
243
244 super.doStop();
245 }
246
247
248
249
250
251
252
253 public List<Request.Listener> getRequestListeners()
254 {
255 return requestListeners;
256 }
257
258
259
260
261 public CookieStore getCookieStore()
262 {
263 return cookieStore;
264 }
265
266
267
268
269 public void setCookieStore(CookieStore cookieStore)
270 {
271 this.cookieStore = Objects.requireNonNull(cookieStore);
272 this.cookieManager = newCookieManager();
273 }
274
275
276
277
278
279
280
281 CookieManager getCookieManager()
282 {
283 return cookieManager;
284 }
285
286
287
288
289 public AuthenticationStore getAuthenticationStore()
290 {
291 return authenticationStore;
292 }
293
294
295
296
297
298
299
300 public Set<ContentDecoder.Factory> getContentDecoderFactories()
301 {
302 return decoderFactories;
303 }
304
305
306
307
308
309
310
311
312 public ContentResponse GET(String uri) throws InterruptedException, ExecutionException, TimeoutException
313 {
314 return GET(URI.create(uri));
315 }
316
317
318
319
320
321
322
323
324 public ContentResponse GET(URI uri) throws InterruptedException, ExecutionException, TimeoutException
325 {
326 return newRequest(uri).send();
327 }
328
329
330
331
332
333
334
335
336 public ContentResponse FORM(String uri, Fields fields) throws InterruptedException, ExecutionException, TimeoutException
337 {
338 return FORM(URI.create(uri), fields);
339 }
340
341
342
343
344
345
346
347
348 public ContentResponse FORM(URI uri, Fields fields) throws InterruptedException, ExecutionException, TimeoutException
349 {
350 return POST(uri).content(new FormContentProvider(fields)).send();
351 }
352
353
354
355
356
357
358
359
360 public Request POST(String uri)
361 {
362 return POST(URI.create(uri));
363 }
364
365
366
367
368
369
370
371 public Request POST(URI uri)
372 {
373 return newRequest(uri).method(HttpMethod.POST);
374 }
375
376
377
378
379
380
381
382
383 public Request newRequest(String host, int port)
384 {
385 return newRequest(new Origin("http", host, port).asString());
386 }
387
388
389
390
391
392
393
394 public Request newRequest(String uri)
395 {
396 return newRequest(URI.create(uri));
397 }
398
399
400
401
402
403
404
405 public Request newRequest(URI uri)
406 {
407 return newHttpRequest(newConversation(), uri);
408 }
409
410 protected Request copyRequest(HttpRequest oldRequest, URI newURI)
411 {
412 Request newRequest = newHttpRequest(oldRequest.getConversation(), newURI);
413 newRequest.method(oldRequest.getMethod())
414 .version(oldRequest.getVersion())
415 .content(oldRequest.getContent())
416 .idleTimeout(oldRequest.getIdleTimeout(), TimeUnit.MILLISECONDS)
417 .timeout(oldRequest.getTimeout(), TimeUnit.MILLISECONDS)
418 .followRedirects(oldRequest.isFollowRedirects());
419 for (HttpField field : oldRequest.getHeaders())
420 {
421 HttpHeader header = field.getHeader();
422
423 if (HttpHeader.HOST == header)
424 continue;
425
426
427 if (HttpHeader.EXPECT == header)
428 continue;
429
430
431 if (HttpHeader.COOKIE == header)
432 continue;
433
434
435 if (HttpHeader.AUTHORIZATION == header ||
436 HttpHeader.PROXY_AUTHORIZATION == header)
437 continue;
438
439 String value = field.getValue();
440 if (!newRequest.getHeaders().contains(header, value))
441 newRequest.header(field.getName(), value);
442 }
443 return newRequest;
444 }
445
446 protected HttpRequest newHttpRequest(HttpConversation conversation, URI uri)
447 {
448 return new HttpRequest(this, conversation, uri);
449 }
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464 public Destination getDestination(String scheme, String host, int port)
465 {
466 return destinationFor(scheme, host, port);
467 }
468
469 protected HttpDestination destinationFor(String scheme, String host, int port)
470 {
471 port = normalizePort(scheme, port);
472
473 Origin origin = new Origin(scheme, host, port);
474 HttpDestination destination = destinations.get(origin);
475 if (destination == null)
476 {
477 destination = transport.newHttpDestination(origin);
478 if (isRunning())
479 {
480 HttpDestination existing = destinations.putIfAbsent(origin, destination);
481 if (existing != null)
482 {
483 destination = existing;
484 }
485 else
486 {
487 if (LOG.isDebugEnabled())
488 LOG.debug("Created {}", destination);
489 }
490 if (!isRunning())
491 destinations.remove(origin);
492 }
493
494 }
495 return destination;
496 }
497
498 protected boolean removeDestination(HttpDestination destination)
499 {
500 return destinations.remove(destination.getOrigin()) != null;
501 }
502
503
504
505
506 public List<Destination> getDestinations()
507 {
508 return new ArrayList<Destination>(destinations.values());
509 }
510
511 protected void send(final HttpRequest request, List<Response.ResponseListener> listeners)
512 {
513 String scheme = request.getScheme().toLowerCase(Locale.ENGLISH);
514 if (!HttpScheme.HTTP.is(scheme) && !HttpScheme.HTTPS.is(scheme))
515 throw new IllegalArgumentException("Invalid protocol " + scheme);
516
517 HttpDestination destination = destinationFor(scheme, request.getHost(), request.getPort());
518 destination.send(request, listeners);
519 }
520
521 protected void newConnection(final HttpDestination destination, final Promise<Connection> promise)
522 {
523 Origin.Address address = destination.getConnectAddress();
524 resolver.resolve(address.getHost(), address.getPort(), new Promise<SocketAddress>()
525 {
526 @Override
527 public void succeeded(SocketAddress socketAddress)
528 {
529 Map<String, Object> context = new HashMap<>();
530 context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
531 context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
532 transport.connect(socketAddress, context);
533 }
534
535 @Override
536 public void failed(Throwable x)
537 {
538 promise.failed(x);
539 }
540 });
541 }
542
543 private HttpConversation newConversation()
544 {
545 return new HttpConversation();
546 }
547
548 protected List<ProtocolHandler> getProtocolHandlers()
549 {
550 return handlers;
551 }
552
553 protected ProtocolHandler findProtocolHandler(Request request, Response response)
554 {
555
556 List<ProtocolHandler> protocolHandlers = getProtocolHandlers();
557 for (int i = 0; i < protocolHandlers.size(); ++i)
558 {
559 ProtocolHandler handler = protocolHandlers.get(i);
560 if (handler.accept(request, response))
561 return handler;
562 }
563 return null;
564 }
565
566
567
568
569 public ByteBufferPool getByteBufferPool()
570 {
571 return byteBufferPool;
572 }
573
574
575
576
577 public void setByteBufferPool(ByteBufferPool byteBufferPool)
578 {
579 this.byteBufferPool = byteBufferPool;
580 }
581
582
583
584
585 public long getConnectTimeout()
586 {
587 return connectTimeout;
588 }
589
590
591
592
593
594 public void setConnectTimeout(long connectTimeout)
595 {
596 this.connectTimeout = connectTimeout;
597 }
598
599
600
601
602 public long getAddressResolutionTimeout()
603 {
604 return addressResolutionTimeout;
605 }
606
607
608
609
610 public void setAddressResolutionTimeout(long addressResolutionTimeout)
611 {
612 this.addressResolutionTimeout = addressResolutionTimeout;
613 }
614
615
616
617
618 public long getIdleTimeout()
619 {
620 return idleTimeout;
621 }
622
623
624
625
626 public void setIdleTimeout(long idleTimeout)
627 {
628 this.idleTimeout = idleTimeout;
629 }
630
631
632
633
634
635 public SocketAddress getBindAddress()
636 {
637 return bindAddress;
638 }
639
640
641
642
643
644
645 public void setBindAddress(SocketAddress bindAddress)
646 {
647 this.bindAddress = bindAddress;
648 }
649
650
651
652
653 public HttpField getUserAgentField()
654 {
655 return agentField;
656 }
657
658
659
660
661 public void setUserAgentField(HttpField agent)
662 {
663 if (agent.getHeader() != HttpHeader.USER_AGENT)
664 throw new IllegalArgumentException();
665 this.agentField = agent;
666 }
667
668
669
670
671
672 public boolean isFollowRedirects()
673 {
674 return followRedirects;
675 }
676
677
678
679
680
681 public void setFollowRedirects(boolean follow)
682 {
683 this.followRedirects = follow;
684 }
685
686
687
688
689 public Executor getExecutor()
690 {
691 return executor;
692 }
693
694
695
696
697 public void setExecutor(Executor executor)
698 {
699 this.executor = executor;
700 }
701
702
703
704
705 public Scheduler getScheduler()
706 {
707 return scheduler;
708 }
709
710
711
712
713 public void setScheduler(Scheduler scheduler)
714 {
715 this.scheduler = scheduler;
716 }
717
718
719
720
721 public int getMaxConnectionsPerDestination()
722 {
723 return maxConnectionsPerDestination;
724 }
725
726
727
728
729
730
731
732
733
734
735
736
737 public void setMaxConnectionsPerDestination(int maxConnectionsPerDestination)
738 {
739 this.maxConnectionsPerDestination = maxConnectionsPerDestination;
740 }
741
742
743
744
745 public int getMaxRequestsQueuedPerDestination()
746 {
747 return maxRequestsQueuedPerDestination;
748 }
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763 public void setMaxRequestsQueuedPerDestination(int maxRequestsQueuedPerDestination)
764 {
765 this.maxRequestsQueuedPerDestination = maxRequestsQueuedPerDestination;
766 }
767
768
769
770
771 public int getRequestBufferSize()
772 {
773 return requestBufferSize;
774 }
775
776
777
778
779 public void setRequestBufferSize(int requestBufferSize)
780 {
781 this.requestBufferSize = requestBufferSize;
782 }
783
784
785
786
787 public int getResponseBufferSize()
788 {
789 return responseBufferSize;
790 }
791
792
793
794
795 public void setResponseBufferSize(int responseBufferSize)
796 {
797 this.responseBufferSize = responseBufferSize;
798 }
799
800
801
802
803
804 public int getMaxRedirects()
805 {
806 return maxRedirects;
807 }
808
809
810
811
812
813 public void setMaxRedirects(int maxRedirects)
814 {
815 this.maxRedirects = maxRedirects;
816 }
817
818
819
820
821 public boolean isTCPNoDelay()
822 {
823 return tcpNoDelay;
824 }
825
826
827
828
829
830 public void setTCPNoDelay(boolean tcpNoDelay)
831 {
832 this.tcpNoDelay = tcpNoDelay;
833 }
834
835
836
837
838
839 public boolean isDispatchIO()
840 {
841 return dispatchIO;
842 }
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857 public void setDispatchIO(boolean dispatchIO)
858 {
859 this.dispatchIO = dispatchIO;
860 }
861
862
863
864
865
866 public boolean isStrictEventOrdering()
867 {
868 return strictEventOrdering;
869 }
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897 public void setStrictEventOrdering(boolean strictEventOrdering)
898 {
899 this.strictEventOrdering = strictEventOrdering;
900 }
901
902
903
904
905
906 public boolean isRemoveIdleDestinations()
907 {
908 return removeIdleDestinations;
909 }
910
911
912
913
914
915
916
917
918
919
920
921
922
923 public void setRemoveIdleDestinations(boolean removeIdleDestinations)
924 {
925 this.removeIdleDestinations = removeIdleDestinations;
926 }
927
928
929
930
931 public ProxyConfiguration getProxyConfiguration()
932 {
933 return proxyConfig;
934 }
935
936 protected HttpField getAcceptEncodingField()
937 {
938 return encodingField;
939 }
940
941 protected String normalizeHost(String host)
942 {
943 if (host != null && host.matches("\\[.*\\]"))
944 return host.substring(1, host.length() - 1);
945 return host;
946 }
947
948 protected int normalizePort(String scheme, int port)
949 {
950 return port > 0 ? port : HttpScheme.HTTPS.is(scheme) ? 443 : 80;
951 }
952
953 protected boolean isDefaultPort(String scheme, int port)
954 {
955 return HttpScheme.HTTPS.is(scheme) ? port == 443 : port == 80;
956 }
957
958 @Override
959 public void dump(Appendable out, String indent) throws IOException
960 {
961 dumpThis(out);
962 dump(out, indent, getBeans(), destinations.values());
963 }
964
965 private class ContentDecoderFactorySet implements Set<ContentDecoder.Factory>
966 {
967 private final Set<ContentDecoder.Factory> set = new HashSet<>();
968
969 @Override
970 public boolean add(ContentDecoder.Factory e)
971 {
972 boolean result = set.add(e);
973 invalidate();
974 return result;
975 }
976
977 @Override
978 public boolean addAll(Collection<? extends ContentDecoder.Factory> c)
979 {
980 boolean result = set.addAll(c);
981 invalidate();
982 return result;
983 }
984
985 @Override
986 public boolean remove(Object o)
987 {
988 boolean result = set.remove(o);
989 invalidate();
990 return result;
991 }
992
993 @Override
994 public boolean removeAll(Collection<?> c)
995 {
996 boolean result = set.removeAll(c);
997 invalidate();
998 return result;
999 }
1000
1001 @Override
1002 public boolean retainAll(Collection<?> c)
1003 {
1004 boolean result = set.retainAll(c);
1005 invalidate();
1006 return result;
1007 }
1008
1009 @Override
1010 public void clear()
1011 {
1012 set.clear();
1013 invalidate();
1014 }
1015
1016 @Override
1017 public int size()
1018 {
1019 return set.size();
1020 }
1021
1022 @Override
1023 public boolean isEmpty()
1024 {
1025 return set.isEmpty();
1026 }
1027
1028 @Override
1029 public boolean contains(Object o)
1030 {
1031 return set.contains(o);
1032 }
1033
1034 @Override
1035 public boolean containsAll(Collection<?> c)
1036 {
1037 return set.containsAll(c);
1038 }
1039
1040 @Override
1041 public Iterator<ContentDecoder.Factory> iterator()
1042 {
1043 final Iterator<ContentDecoder.Factory> iterator = set.iterator();
1044 return new Iterator<ContentDecoder.Factory>()
1045 {
1046 @Override
1047 public boolean hasNext()
1048 {
1049 return iterator.hasNext();
1050 }
1051
1052 @Override
1053 public ContentDecoder.Factory next()
1054 {
1055 return iterator.next();
1056 }
1057
1058 @Override
1059 public void remove()
1060 {
1061 iterator.remove();
1062 invalidate();
1063 }
1064 };
1065 }
1066
1067 @Override
1068 public Object[] toArray()
1069 {
1070 return set.toArray();
1071 }
1072
1073 @Override
1074 public <T> T[] toArray(T[] a)
1075 {
1076 return set.toArray(a);
1077 }
1078
1079 private void invalidate()
1080 {
1081 if (set.isEmpty())
1082 {
1083 encodingField = null;
1084 }
1085 else
1086 {
1087 StringBuilder value = new StringBuilder();
1088 for (Iterator<ContentDecoder.Factory> iterator = set.iterator(); iterator.hasNext();)
1089 {
1090 ContentDecoder.Factory decoderFactory = iterator.next();
1091 value.append(decoderFactory.getEncoding());
1092 if (iterator.hasNext())
1093 value.append(",");
1094 }
1095 encodingField = new HttpField(HttpHeader.ACCEPT_ENCODING, value.toString());
1096 }
1097 }
1098 }
1099 }