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