1 // ======================================================================== 2 // Copyright 2006-2007 Mort Bay Consulting Pty. Ltd. 3 // ------------------------------------------------------------------------ 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 // ======================================================================== 14 15 package org.mortbay.jetty.client; 16 17 import java.io.IOException; 18 import java.io.InputStream; 19 import java.net.UnknownHostException; 20 import java.security.KeyStore; 21 import java.security.SecureRandom; 22 import java.util.HashMap; 23 import java.util.LinkedList; 24 import java.util.Map; 25 import java.util.Set; 26 27 import javax.net.ssl.HostnameVerifier; 28 import javax.net.ssl.KeyManager; 29 import javax.net.ssl.KeyManagerFactory; 30 import javax.net.ssl.SSLContext; 31 import javax.net.ssl.SSLSession; 32 import javax.net.ssl.TrustManager; 33 import javax.net.ssl.TrustManagerFactory; 34 import javax.net.ssl.X509TrustManager; 35 36 import org.mortbay.component.LifeCycle; 37 import org.mortbay.io.Buffer; 38 import org.mortbay.io.ByteArrayBuffer; 39 import org.mortbay.io.nio.DirectNIOBuffer; 40 import org.mortbay.io.nio.IndirectNIOBuffer; 41 import org.mortbay.io.nio.NIOBuffer; 42 import org.mortbay.jetty.AbstractBuffers; 43 import org.mortbay.jetty.HttpSchemes; 44 import org.mortbay.jetty.client.security.Authorization; 45 import org.mortbay.jetty.client.security.RealmResolver; 46 import org.mortbay.log.Log; 47 import org.mortbay.resource.Resource; 48 import org.mortbay.thread.QueuedThreadPool; 49 import org.mortbay.thread.ThreadPool; 50 import org.mortbay.thread.Timeout; 51 52 /** 53 * Http Client. 54 * <p/> 55 * HttpClient is the main active component of the client API implementation. 56 * It is the opposite of the Connectors in standard Jetty, in that it listens 57 * for responses rather than requests. Just like the connectors, there is a 58 * blocking socket version and a non-blocking NIO version (implemented as nested classes 59 * selected by {@link #setConnectorType(int)}). 60 * <p/> 61 * The an instance of {@link HttpExchange} is passed to the {@link #send(HttpExchange)} method 62 * to send a request. The exchange contains both the headers and content (source) of the request 63 * plus the callbacks to handle responses. A HttpClient can have many exchanges outstanding 64 * and they may be queued on the {@link HttpDestination} waiting for a {@link HttpConnection}, 65 * queued in the {@link HttpConnection} waiting to be transmitted or pipelined on the actual 66 * TCP/IP connection waiting for a response. 67 * <p/> 68 * The {@link HttpDestination} class is an aggregation of {@link HttpConnection}s for the 69 * same host, port and protocol. A destination may limit the number of connections 70 * open and they provide a pool of open connections that may be reused. Connections may also 71 * be allocated from a destination, so that multiple request sources are not multiplexed 72 * over the same connection. 73 * 74 * @see {@link HttpExchange} 75 * @see {@link HttpDestination} 76 * @author Greg Wilkins 77 * @author Matthew Purland 78 * @author Guillaume Nodet 79 */ 80 public class HttpClient extends AbstractBuffers 81 { 82 public static final int CONNECTOR_SOCKET=0; 83 public static final int CONNECTOR_SELECT_CHANNEL=2; 84 85 private int _connectorType=CONNECTOR_SELECT_CHANNEL; 86 private boolean _useDirectBuffers=true; 87 private int _maxConnectionsPerAddress=32; 88 private Map<Address, HttpDestination> _destinations = new HashMap<Address, HttpDestination>(); 89 ThreadPool _threadPool; 90 Connector _connector; 91 private long _idleTimeout=20000; 92 private long _timeout=320000; 93 int _soTimeout = 10000; 94 private Timeout _timeoutQ = new Timeout(); 95 private Address _proxy; 96 private Authorization _proxyAuthentication; 97 private Set<String> _noProxy; 98 private int _maxRetries = 3; 99 private LinkedList<String> _registeredListeners; 100 101 // TODO clean up and add getters/setters to some of this maybe 102 private String _keyStoreLocation; 103 private String _keyStoreType="JKS"; 104 private String _keyStorePassword; 105 private String _keyManagerAlgorithm = "SunX509"; 106 private String _keyManagerPassword; 107 private String _trustStoreLocation; 108 private String _trustStoreType="JKS"; 109 private String _trustStorePassword; 110 private String _trustManagerAlgorithm = "SunX509"; 111 112 private SSLContext _sslContext; 113 114 private String _protocol="TLS"; 115 private String _provider; 116 private String _secureRandomAlgorithm; 117 118 private RealmResolver _realmResolver; 119 dump()120 public void dump() throws IOException 121 { 122 for (Map.Entry<Address, HttpDestination> entry : _destinations.entrySet()) 123 { 124 System.err.println("\n"+entry.getKey()+":"); 125 entry.getValue().dump(); 126 } 127 } 128 129 /* ------------------------------------------------------------------------------- */ send(HttpExchange exchange)130 public void send(HttpExchange exchange) throws IOException 131 { 132 boolean ssl=HttpSchemes.HTTPS_BUFFER.equalsIgnoreCase(exchange.getScheme()); 133 exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_CONNECTION); 134 HttpDestination destination=getDestination(exchange.getAddress(),ssl); 135 destination.send(exchange); 136 } 137 138 /* ------------------------------------------------------------ */ 139 /** 140 * @return the threadPool 141 */ getThreadPool()142 public ThreadPool getThreadPool() 143 { 144 return _threadPool; 145 } 146 147 /* ------------------------------------------------------------ */ 148 /** 149 * @param threadPool the threadPool to set 150 */ setThreadPool(ThreadPool threadPool)151 public void setThreadPool(ThreadPool threadPool) 152 { 153 _threadPool=threadPool; 154 } 155 156 /* ------------------------------------------------------------------------------- */ getDestination(Address remote, boolean ssl)157 public HttpDestination getDestination(Address remote, boolean ssl) throws UnknownHostException, IOException 158 { 159 if (remote==null) 160 throw new UnknownHostException("Remote socket address cannot be null."); 161 162 synchronized (_destinations) 163 { 164 HttpDestination destination=_destinations.get(remote); 165 if (destination==null) 166 { 167 destination=new HttpDestination(this,remote,ssl,_maxConnectionsPerAddress); 168 if (_proxy != null && (_noProxy == null || !_noProxy.contains(remote.getHost()))) 169 { 170 destination.setProxy(_proxy); 171 if (_proxyAuthentication!=null) 172 destination.setProxyAuthentication(_proxyAuthentication); 173 } 174 _destinations.put(remote,destination); 175 } 176 return destination; 177 } 178 } 179 180 /* ------------------------------------------------------------ */ schedule(Timeout.Task task)181 public void schedule(Timeout.Task task) 182 { 183 _timeoutQ.schedule(task); 184 } 185 186 /* ------------------------------------------------------------ */ cancel(Timeout.Task task)187 public void cancel(Timeout.Task task) 188 { 189 task.cancel(); 190 } 191 192 /* ------------------------------------------------------------ */ 193 /** 194 * Get whether the connector can use direct NIO buffers. 195 */ getUseDirectBuffers()196 public boolean getUseDirectBuffers() 197 { 198 return _useDirectBuffers; 199 } 200 201 /* ------------------------------------------------------------ */ setRealmResolver( RealmResolver resolver )202 public void setRealmResolver( RealmResolver resolver ) 203 { 204 _realmResolver = resolver; 205 } 206 207 /* ------------------------------------------------------------ */ 208 /** 209 * returns the SecurityRealmResolver registered with the HttpClient or null 210 * 211 * @return 212 */ getRealmResolver()213 public RealmResolver getRealmResolver() 214 { 215 return _realmResolver; 216 } 217 218 /* ------------------------------------------------------------ */ hasRealms()219 public boolean hasRealms() 220 { 221 return _realmResolver==null?false:true; 222 } 223 224 225 /** 226 * Registers a listener that can listen to the stream of execution between the client and the 227 * server and influence events. Sequential calls to the method wrapper sequentially wrap the preceeding 228 * listener in a delegation model. 229 * <p/> 230 * NOTE: the SecurityListener is a special listener which doesn't need to be added via this 231 * mechanic, if you register security realms then it will automatically be added as the top listener of the 232 * delegation stack. 233 * 234 * @param listenerClass 235 */ registerListener( String listenerClass )236 public void registerListener( String listenerClass ) 237 { 238 if ( _registeredListeners == null ) 239 { 240 _registeredListeners = new LinkedList<String>(); 241 } 242 _registeredListeners.add( listenerClass ); 243 } 244 getRegisteredListeners()245 public LinkedList<String> getRegisteredListeners() 246 { 247 return _registeredListeners; 248 } 249 250 251 /* ------------------------------------------------------------ */ 252 /** 253 * Set to use NIO direct buffers. 254 * 255 * @param direct 256 * If True (the default), the connector can use NIO direct 257 * buffers. Some JVMs have memory management issues (bugs) with 258 * direct buffers. 259 */ setUseDirectBuffers(boolean direct)260 public void setUseDirectBuffers(boolean direct) 261 { 262 _useDirectBuffers=direct; 263 } 264 265 /* ------------------------------------------------------------ */ 266 /** 267 * Get the type of connector (socket, blocking or select) in use. 268 */ getConnectorType()269 public int getConnectorType() 270 { 271 return _connectorType; 272 } 273 274 /* ------------------------------------------------------------ */ setConnectorType(int connectorType)275 public void setConnectorType(int connectorType) 276 { 277 this._connectorType=connectorType; 278 } 279 280 /* ------------------------------------------------------------ */ 281 /** 282 * Create a new NIO buffer. If using direct buffers, it will create a direct 283 * NIO buffer, other than an indirect buffer. 284 */ 285 @Override newBuffer(int size)286 protected Buffer newBuffer(int size) 287 { 288 if (_connectorType!=CONNECTOR_SOCKET) 289 { 290 Buffer buf=null; 291 if (size==getHeaderBufferSize()) 292 buf=new IndirectNIOBuffer(size); 293 else if (_useDirectBuffers) 294 buf=new DirectNIOBuffer(size); 295 else 296 buf=new IndirectNIOBuffer(size); 297 return buf; 298 } 299 else 300 { 301 return new ByteArrayBuffer(size); 302 } 303 } 304 305 /* ------------------------------------------------------------ */ getMaxConnectionsPerAddress()306 public int getMaxConnectionsPerAddress() 307 { 308 return _maxConnectionsPerAddress; 309 } 310 311 /* ------------------------------------------------------------ */ setMaxConnectionsPerAddress(int maxConnectionsPerAddress)312 public void setMaxConnectionsPerAddress(int maxConnectionsPerAddress) 313 { 314 _maxConnectionsPerAddress=maxConnectionsPerAddress; 315 } 316 317 /* ------------------------------------------------------------ */ doStart()318 protected void doStart() throws Exception 319 { 320 super.doStart(); 321 322 _timeoutQ.setNow(); 323 _timeoutQ.setDuration(_timeout); 324 325 if(_threadPool==null) 326 { 327 QueuedThreadPool pool = new QueuedThreadPool(); 328 pool.setMaxThreads(16); 329 pool.setDaemon(true); 330 pool.setName("HttpClient"); 331 _threadPool=pool; 332 } 333 334 if (_threadPool instanceof LifeCycle) 335 { 336 ((LifeCycle)_threadPool).start(); 337 } 338 339 340 if (_connectorType==CONNECTOR_SELECT_CHANNEL) 341 { 342 343 _connector=new SelectConnector(this); 344 } 345 else 346 { 347 _connector=new SocketConnector(this); 348 } 349 _connector.start(); 350 351 _threadPool.dispatch(new Runnable() 352 { 353 public void run() 354 { 355 while (isStarted()) 356 { 357 _timeoutQ.setNow(); 358 _timeoutQ.tick(); 359 try 360 { 361 Thread.sleep(1000); 362 } 363 catch (InterruptedException e) 364 { 365 } 366 } 367 } 368 }); 369 370 } 371 372 /* ------------------------------------------------------------ */ doStop()373 protected void doStop() throws Exception 374 { 375 _connector.stop(); 376 _connector=null; 377 if (_threadPool instanceof LifeCycle) 378 { 379 ((LifeCycle)_threadPool).stop(); 380 } 381 for (HttpDestination destination : _destinations.values()) 382 { 383 destination.close(); 384 } 385 386 _timeoutQ.cancelAll(); 387 super.doStop(); 388 } 389 390 /* ------------------------------------------------------------ */ 391 interface Connector extends LifeCycle 392 { startConnection(HttpDestination destination)393 public void startConnection(HttpDestination destination) throws IOException; 394 395 } 396 397 /** 398 * if a keystore location has been provided then client will attempt to use it as the keystore, 399 * otherwise we simply ignore certificates and run with a loose ssl context. 400 * 401 * @return 402 * @throws IOException 403 */ getSSLContext()404 protected SSLContext getSSLContext() throws IOException 405 { 406 if (_sslContext == null) 407 { 408 if (_keyStoreLocation == null) 409 { 410 _sslContext = getLooseSSLContext(); 411 } 412 else 413 { 414 _sslContext = getStrictSSLContext(); 415 } 416 } 417 return _sslContext; 418 } 419 getStrictSSLContext()420 protected SSLContext getStrictSSLContext() throws IOException 421 { 422 423 try 424 { 425 if (_trustStoreLocation==null) 426 { 427 _trustStoreLocation=_keyStoreLocation; 428 _trustStoreType=_keyStoreType; 429 } 430 431 KeyManager[] keyManagers=null; 432 InputStream keystoreInputStream = null; 433 434 keystoreInputStream= Resource.newResource(_keyStoreLocation).getInputStream(); 435 KeyStore keyStore=KeyStore.getInstance(_keyStoreType); 436 keyStore.load(keystoreInputStream,_keyStorePassword==null?null:_keyStorePassword.toString().toCharArray()); 437 438 KeyManagerFactory keyManagerFactory=KeyManagerFactory.getInstance(_keyManagerAlgorithm); 439 keyManagerFactory.init(keyStore,_keyManagerPassword==null?null:_keyManagerPassword.toString().toCharArray()); 440 keyManagers=keyManagerFactory.getKeyManagers(); 441 442 TrustManager[] trustManagers=null; 443 InputStream truststoreInputStream = null; 444 445 truststoreInputStream = Resource.newResource(_trustStoreLocation).getInputStream(); 446 KeyStore trustStore=KeyStore.getInstance(_trustStoreType); 447 trustStore.load(truststoreInputStream,_trustStorePassword==null?null:_trustStorePassword.toString().toCharArray()); 448 449 TrustManagerFactory trustManagerFactory=TrustManagerFactory.getInstance(_trustManagerAlgorithm); 450 trustManagerFactory.init(trustStore); 451 trustManagers=trustManagerFactory.getTrustManagers(); 452 453 SecureRandom secureRandom=_secureRandomAlgorithm==null?null:SecureRandom.getInstance(_secureRandomAlgorithm); 454 SSLContext context=_provider==null?SSLContext.getInstance(_protocol):SSLContext.getInstance(_protocol,_provider); 455 context.init(keyManagers,trustManagers,secureRandom); 456 return context; 457 } 458 catch ( Exception e ) 459 { 460 e.printStackTrace(); 461 throw new IOException( "error generating ssl context for " + _keyStoreLocation + " " + e.getMessage() ); 462 } 463 } 464 getLooseSSLContext()465 protected SSLContext getLooseSSLContext() throws IOException 466 { 467 468 // Create a trust manager that does not validate certificate 469 // chains 470 TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() 471 { 472 public java.security.cert.X509Certificate[] getAcceptedIssuers() 473 { 474 return null; 475 } 476 477 public void checkClientTrusted( java.security.cert.X509Certificate[] certs, String authType ) 478 { 479 } 480 481 public void checkServerTrusted( java.security.cert.X509Certificate[] certs, String authType ) 482 { 483 } 484 } }; 485 486 HostnameVerifier hostnameVerifier = new HostnameVerifier() 487 { 488 public boolean verify( String urlHostName, SSLSession session ) 489 { 490 Log.warn( "Warning: URL Host: " + urlHostName + " vs." + session.getPeerHost() ); 491 return true; 492 } 493 }; 494 495 // Install the all-trusting trust manager 496 try 497 { 498 // TODO real trust manager 499 SSLContext sslContext = SSLContext.getInstance( "SSL" ); 500 sslContext.init( null, trustAllCerts, new java.security.SecureRandom() ); 501 return sslContext; 502 } 503 catch ( Exception e ) 504 { 505 throw new IOException( "issue ignoring certs" ); 506 } 507 } 508 509 /* ------------------------------------------------------------ */ 510 /** 511 * @return the period in milliseconds a {@link HttpConnection} can be idle for before it is closed. 512 */ getIdleTimeout()513 public long getIdleTimeout() 514 { 515 return _idleTimeout; 516 } 517 518 /* ------------------------------------------------------------ */ 519 /** 520 * @param ms the period in milliseconds a {@link HttpConnection} can be idle for before it is closed. 521 */ setIdleTimeout(long ms)522 public void setIdleTimeout(long ms) 523 { 524 _idleTimeout=ms; 525 } 526 527 /* ------------------------------------------------------------ */ getSoTimeout()528 public int getSoTimeout() 529 { 530 return _soTimeout; 531 } 532 533 /* ------------------------------------------------------------ */ setSoTimeout(int so)534 public void setSoTimeout(int so) 535 { 536 _soTimeout = so; 537 } 538 539 /* ------------------------------------------------------------ */ 540 /** 541 * @return the period in ms that an exchange will wait for a response from the server. 542 */ getTimeout()543 public long getTimeout() 544 { 545 return _timeout; 546 } 547 548 /* ------------------------------------------------------------ */ 549 /** 550 * @param ms the period in ms that an exchange will wait for a response from the server. 551 */ setTimeout(long ms)552 public void setTimeout(long ms) 553 { 554 _timeout=ms; 555 } 556 557 /* ------------------------------------------------------------ */ getProxy()558 public Address getProxy() 559 { 560 return _proxy; 561 } 562 563 /* ------------------------------------------------------------ */ setProxy(Address proxy)564 public void setProxy(Address proxy) 565 { 566 this._proxy = proxy; 567 } 568 569 /* ------------------------------------------------------------ */ getProxyAuthentication()570 public Authorization getProxyAuthentication() 571 { 572 return _proxyAuthentication; 573 } 574 575 /* ------------------------------------------------------------ */ setProxyAuthentication(Authorization authentication)576 public void setProxyAuthentication(Authorization authentication) 577 { 578 _proxyAuthentication = authentication; 579 } 580 581 /* ------------------------------------------------------------ */ isProxied()582 public boolean isProxied() 583 { 584 return this._proxy!=null; 585 } 586 587 /* ------------------------------------------------------------ */ getNoProxy()588 public Set<String> getNoProxy() 589 { 590 return _noProxy; 591 } 592 593 /* ------------------------------------------------------------ */ setNoProxy(Set<String> noProxyAddresses)594 public void setNoProxy(Set<String> noProxyAddresses) 595 { 596 _noProxy = noProxyAddresses; 597 } 598 599 /* ------------------------------------------------------------ */ maxRetries()600 public int maxRetries() 601 { 602 return _maxRetries; 603 } 604 605 /* ------------------------------------------------------------ */ setMaxRetries( int retries )606 public void setMaxRetries( int retries ) 607 { 608 _maxRetries = retries; 609 } 610 getTrustStoreLocation()611 public String getTrustStoreLocation() 612 { 613 return _trustStoreLocation; 614 } 615 setTrustStoreLocation(String trustStoreLocation)616 public void setTrustStoreLocation(String trustStoreLocation) 617 { 618 this._trustStoreLocation = trustStoreLocation; 619 } 620 getKeyStoreLocation()621 public String getKeyStoreLocation() 622 { 623 return _keyStoreLocation; 624 } 625 setKeyStoreLocation(String keyStoreLocation)626 public void setKeyStoreLocation(String keyStoreLocation) 627 { 628 this._keyStoreLocation = keyStoreLocation; 629 } 630 setKeyStorePassword(String _keyStorePassword)631 public void setKeyStorePassword(String _keyStorePassword) 632 { 633 this._keyStorePassword = _keyStorePassword; 634 } 635 setKeyManagerPassword(String _keyManagerPassword)636 public void setKeyManagerPassword(String _keyManagerPassword) 637 { 638 this._keyManagerPassword = _keyManagerPassword; 639 } 640 setTrustStorePassword(String _trustStorePassword)641 public void setTrustStorePassword(String _trustStorePassword) 642 { 643 this._trustStorePassword = _trustStorePassword; 644 } 645 } 646