View Javadoc
1   /*
2     (C) Copyright IBM Corp. 2005, 2013
3   
4     THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE
5     ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE
6     CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT.
7   
8     You can obtain a current copy of the Eclipse Public License from
9     http://www.opensource.org/licenses/eclipse-1.0.php
10  
11    @author : Roberto Pineiro, IBM, roberto.pineiro@us.ibm.com
12   * @author : Chung-hao Tan, IBM, chungtan@us.ibm.com
13   * 
14   * 
15   * Change History
16   * Flag       Date        Prog         Description
17   *------------------------------------------------------------------------------- 
18   * 1422316    2005-05-08  lupusalex    Disable delayed acknowledgment
19   * 1483270    2006-05-15  lupusalex    Using Several Cim Clients cause an indication problem
20   * 1498130    2006-05-31  lupusalex    Selection of xml parser on a per connection basis
21   * 1535756    2006-08-07  lupusalex    Make code warning free
22   * 1565892    2006-11-28  lupusalex    Make SBLIM client JSR48 compliant
23   * 1649779    2007-02-01  lupusalex    Indication listener threads freeze
24   * 1660743    2007-02-15  lupusalex    SSLContext is static
25   * 2003590    2008-06-30  blaschke-oss Change licensing from CPL to EPL
26   * 2204488 	  2008-10-28  raman_arora  Fix code to remove compiler warnings
27   * 2210455    2008-10-30  blaschke-oss Enhance javadoc, fix potential null pointers
28   * 2524131    2009-01-21  raman_arora  Upgrade client to JDK 1.5 (Phase 1)
29   * 2531371    2009-02-10  raman_arora  Upgrade client to JDK 1.5 (Phase 2)
30   * 3027392    2010-07-09  blaschke-oss Nullcheck of value previously dereferenced
31   * 3206904    2011-03-11  lupusalex    Indication listener deadlock causes JVM to run out sockets
32   * 3536399    2012-08-25  hellerda     Add client/listener peer authentication properties
33   *    2618    2013-02-27  blaschke-oss Need to add property to disable weak cipher suites for the secure indication
34   *    2642    2013-05-21  blaschke-oss Seperate properties needed for cim client and listener to filter out ciphers
35   */
36  
37  package org.metricshub.wbem.sblim.cimclient.internal.http;
38  
39  /*-
40   * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
41   * WBEM Java Client
42   * ჻჻჻჻჻჻
43   * Copyright 2023 - 2025 MetricsHub
44   * ჻჻჻჻჻჻
45   * Licensed under the Apache License, Version 2.0 (the "License");
46   * you may not use this file except in compliance with the License.
47   * You may obtain a copy of the License at
48   *
49   *      http://www.apache.org/licenses/LICENSE-2.0
50   *
51   * Unless required by applicable law or agreed to in writing, software
52   * distributed under the License is distributed on an "AS IS" BASIS,
53   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
54   * See the License for the specific language governing permissions and
55   * limitations under the License.
56   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
57   */
58  
59  import java.io.IOException;
60  import java.net.InetAddress;
61  import java.net.ServerSocket;
62  import java.net.Socket;
63  import java.net.SocketException;
64  import java.net.UnknownHostException;
65  import java.util.concurrent.ArrayBlockingQueue;
66  import java.util.concurrent.BlockingQueue;
67  import java.util.concurrent.TimeUnit;
68  import java.util.logging.Level;
69  import javax.net.ssl.SSLContext;
70  import javax.net.ssl.SSLServerSocket;
71  import org.metricshub.wbem.sblim.cimclient.internal.logging.LogAndTraceBroker;
72  import org.metricshub.wbem.sblim.cimclient.internal.util.ThreadPool;
73  import org.metricshub.wbem.sblim.cimclient.internal.util.Util;
74  import org.metricshub.wbem.sblim.cimclient.internal.util.WBEMConfiguration;
75  
76  /**
77   * Class HttpServerConnection implements the outer shell of a HTTP server. It
78   * accepts incoming connections and puts them in a queue to be serviced by an
79   * independent thread
80   *
81   */
82  public class HttpServerConnection implements Runnable {
83  	private int iPort;
84  
85  	private ServerSocket iServerSocket;
86  
87  	private HttpConnectionHandler iHandler;
88  
89  	private HttpConnectionDispatcher iDispatcher;
90  
91  	private volatile boolean iClose = true;
92  
93  	private String iServerName;
94  
95  	private boolean iSsl;
96  
97  	private Thread iRunner;
98  
99  	private WBEMConfiguration iSessionProperties;
100 
101 	private final int iTimeout;
102 
103 	/**
104 	 * Ctor.
105 	 *
106 	 * @param pHandler
107 	 *            The connection handler
108 	 * @param pLocalAddress
109 	 *            The local address to bind the port to. If null the port is
110 	 *            bound to all local addresses. For use on multi-homed systems
111 	 * @param pPort
112 	 *            The local port. If zero any free port will be chosen.
113 	 * @param pSsl
114 	 *            SSL secured connection ?
115 	 * @param pProperties
116 	 *            The configuration context
117 	 * @throws IOException
118 	 */
119 	public HttpServerConnection(
120 		HttpConnectionHandler pHandler,
121 		String pLocalAddress,
122 		int pPort,
123 		boolean pSsl,
124 		WBEMConfiguration pProperties
125 	)
126 		throws IOException {
127 		final LogAndTraceBroker logger = LogAndTraceBroker.getBroker();
128 		logger.entry();
129 
130 		this.iPort = pPort;
131 		this.iHandler = pHandler;
132 		this.iSsl = pSsl;
133 		this.iServerName = pSsl ? "HTTPS Server" : "HTTP Server";
134 		this.iSessionProperties = (pProperties != null) ? pProperties : WBEMConfiguration.getGlobalConfiguration();
135 		SSLContext sslContext = pSsl ? HttpSocketFactory.getInstance().getServerSSLContext(this.iSessionProperties) : null;
136 		this.iServerSocket =
137 			(pLocalAddress != null && pLocalAddress.length() > 0)
138 				? HttpSocketFactory
139 					.getInstance()
140 					.getServerSocketFactory(sslContext)
141 					.createServerSocket(pPort, 50, InetAddress.getByName(pLocalAddress))
142 				: HttpSocketFactory.getInstance().getServerSocketFactory(sslContext).createServerSocket(pPort);
143 		if (this.iServerSocket instanceof SSLServerSocket) {
144 			if (this.iSessionProperties.getSslListenerPeerVerification().equalsIgnoreCase("ignore")) {
145 				logger.trace(Level.FINER, "Listener peer verification: ignore");
146 				((SSLServerSocket) this.iServerSocket).setNeedClientAuth(false);
147 			} else if (this.iSessionProperties.getSslListenerPeerVerification().equalsIgnoreCase("accept")) {
148 				logger.trace(Level.FINER, "Listener peer verification: accept");
149 				((SSLServerSocket) this.iServerSocket).setWantClientAuth(true);
150 			} else {
151 				logger.trace(Level.FINER, "Listener peer verification: require");
152 				((SSLServerSocket) this.iServerSocket).setNeedClientAuth(true);
153 			}
154 
155 			String disableCipherSuites = this.iSessionProperties.getSslListenerCipherSuitesToDisable();
156 			if (disableCipherSuites != null) {
157 				SSLServerSocket sslSock = (SSLServerSocket) this.iServerSocket;
158 				String[] currentCipherSuites = sslSock.getEnabledCipherSuites();
159 				String[] updatedCipherSuites = Util.getFilteredStringArray(currentCipherSuites, disableCipherSuites);
160 				sslSock.setEnabledCipherSuites(updatedCipherSuites);
161 				int before = currentCipherSuites.length;
162 				int after = updatedCipherSuites.length;
163 				if (before > 0 && after == 0) logger.trace(Level.WARNING, "All cipher suites disabled for listener!"); else if (
164 					before > after
165 				) logger.trace(Level.FINE, "Some (" + (before - after) + ") cipher suites disabled for listener"); else if (
166 					before == after
167 				) logger.trace(Level.FINER, "No cipher suites disabled for listener");
168 			}
169 		}
170 		this.iTimeout = this.iSessionProperties.getListenerHttpTimeout();
171 		logger.exit();
172 	}
173 
174 	/**
175 	 * Set the name of the thread
176 	 *
177 	 * @param pName
178 	 *            The new value
179 	 */
180 	public void setName(String pName) {
181 		if (this.iRunner != null) this.iRunner.setName(pName);
182 	}
183 
184 	/**
185 	 * Returns the port
186 	 *
187 	 * @return The port
188 	 */
189 	public int getPort() {
190 		return this.iServerSocket.getLocalPort();
191 	}
192 
193 	/**
194 	 * Returns the local ip address the socket is bound to
195 	 *
196 	 * @return The ip address
197 	 * @throws UnknownHostException
198 	 */
199 	public String getLocalIp() throws UnknownHostException {
200 		String ip = this.iServerSocket.getInetAddress().getHostAddress();
201 		String localhost = InetAddress.getLocalHost().getHostAddress();
202 		return "0.0.0.0".equals(ip) ? localhost : ip;
203 	}
204 
205 	/**
206 	 * Returns the local hostname the socket is bound to
207 	 *
208 	 * @return The host name
209 	 * @throws UnknownHostException
210 	 */
211 	public String getLocalHostName() throws UnknownHostException {
212 		String ip = this.iServerSocket.getInetAddress().getHostName();
213 		String localhost = InetAddress.getLocalHost().getHostName();
214 		return "0.0.0.0".equals(ip) ? localhost : ip;
215 	}
216 
217 	/**
218 	 * Return whether this connection is SSL secured
219 	 *
220 	 * @return <code>true</code> if SSL is enabled, <code>false</code> otherwise
221 	 */
222 	public boolean isSSL() {
223 		return this.iSsl;
224 	}
225 
226 	/**
227 	 * Starts a thread that waits for incoming connections
228 	 */
229 	public void start() {
230 		if (this.iClose) {
231 			this.iClose = false;
232 			ThreadGroup group = new ThreadGroup("CIMListener on port " + String.valueOf(this.iPort));
233 			this.iDispatcher =
234 				new HttpConnectionDispatcher(
235 					group,
236 					this.iHandler,
237 					new ThreadPool(
238 						this.iSessionProperties.getListenerMinPoolSize(),
239 						this.iSessionProperties.getListenerMaxPoolSize(),
240 						this.iSessionProperties.getListenerBacklog(),
241 						this.iSessionProperties.getListenerMaxIdle(),
242 						group,
243 						"Handler "
244 					),
245 					this.iSessionProperties.getListenerMaxQueueSize()
246 				);
247 			this.iDispatcher.start();
248 			this.iRunner = new Thread(group, this, this.iServerName);
249 			this.iRunner.setDaemon(true);
250 
251 			this.iRunner.start();
252 		}
253 	}
254 
255 	public void run() {
256 		while (!this.iClose) {
257 			try {
258 				Socket socket = this.iServerSocket.accept();
259 				try {
260 					socket.setTcpNoDelay(true);
261 					socket.setSoTimeout(this.iTimeout);
262 				} catch (IOException e) {
263 					LogAndTraceBroker.getBroker().trace(Level.FINE, "Exception while adjusting socket options", e);
264 				}
265 				boolean dispatched = this.iDispatcher.dispatch(socket);
266 				if (!dispatched) {
267 					MessageWriter writer = new MessageWriter(socket.getOutputStream(), false, false);
268 					try {
269 						writer.setMethod(new HttpServerMethod(1, 1, 503, "Service temporarily overloaded"));
270 						writer.getHeader().addField("Retry-After", "10");
271 						writer.close();
272 					} catch (IOException e) {
273 						LogAndTraceBroker.getBroker().trace(Level.FINE, "Exception while sending HTTP 503", e);
274 					} finally {
275 						socket.close();
276 					}
277 					LogAndTraceBroker
278 						.getBroker()
279 						.trace(Level.FINE, "HttpServerConnection failed to dispatch incoming connection, sent 503");
280 				} else {
281 					LogAndTraceBroker.getBroker().trace(Level.FINE, "HttpServerConnection dispatched incoming connection");
282 				}
283 			} catch (Throwable t) {
284 				if (t instanceof SocketException && this.iClose) {
285 					break;
286 				}
287 				try {
288 					LogAndTraceBroker.getBroker().trace(Level.FINE, "Exception while waiting for incoming http connections");
289 				} catch (Throwable t2) {
290 					// just give up
291 				}
292 			}
293 		}
294 
295 		// shutdown
296 
297 		try {
298 			LogAndTraceBroker.getBroker().trace(Level.FINE, "Shutting down CIMListener on port " + this.iPort);
299 		} catch (Throwable t) {
300 			// do nothing
301 		}
302 		try {
303 			// give the handlers a chance to process all already accepted
304 			// connections before complete shutdown
305 			Thread.sleep(5000);
306 		} catch (InterruptedException e) {
307 			// just ignore
308 		}
309 		try {
310 			this.iDispatcher.close();
311 		} catch (Exception e) {
312 			LogAndTraceBroker.getBroker().trace(Level.FINE, "Exception while closing http connection dispatcher", e);
313 		}
314 		this.iDispatcher = null;
315 		this.iRunner = null;
316 	}
317 
318 	/**
319 	 * Closes the socket and shuts down the listening threads
320 	 */
321 	public void close() {
322 		if (!this.iClose) {
323 			this.iClose = true;
324 			try {
325 				this.iServerSocket.close();
326 				this.iServerSocket = null;
327 			} catch (Exception e) {
328 				LogAndTraceBroker.getBroker().trace(Level.FINE, "Exception while closing server socket", e);
329 			}
330 		}
331 	}
332 
333 	/**
334 	 * Class HttpConnectionDispatcher is responsible for dispatching the
335 	 * incoming connections to the handlers. It doesn't execute the handler
336 	 * directly but creates a runnable that is submitted to a thread pool which
337 	 * takes care of execution.
338 	 *
339 	 */
340 	private static class HttpConnectionDispatcher extends Thread {
341 		private BlockingQueue<Socket> iConnectionPool;
342 
343 		private volatile boolean iAlive = true;
344 
345 		private HttpConnectionHandler iHandler;
346 
347 		private ThreadPool iThreadPool;
348 
349 		/**
350 		 * Ctor.
351 		 *
352 		 * @param pGroup
353 		 *            The thread group to use for this thread and it's children
354 		 * @param pHandler
355 		 *            The connection handler
356 		 * @param pPool
357 		 *            The thread pool
358 		 * @param pQueueSize
359 		 *            The fixed capacity for the queue of pending connections
360 		 */
361 		public HttpConnectionDispatcher(
362 			ThreadGroup pGroup,
363 			HttpConnectionHandler pHandler,
364 			ThreadPool pPool,
365 			int pQueueSize
366 		) {
367 			super(pGroup, "Connection Dispatcher");
368 			setDaemon(true);
369 			this.iConnectionPool = new ArrayBlockingQueue<Socket>(pQueueSize > 0 ? pQueueSize : 1);
370 			this.iHandler = pHandler;
371 			this.iThreadPool = pPool;
372 		}
373 
374 		/**
375 		 * Dispatches a connection
376 		 *
377 		 * @param pSocket
378 		 *            The socket of the connection
379 		 * @return true if dispatch was successful
380 		 */
381 		public boolean dispatch(Socket pSocket) {
382 			try {
383 				return this.iConnectionPool.offer(pSocket, 20, TimeUnit.MILLISECONDS);
384 			} catch (InterruptedException e) {
385 				/* */
386 			}
387 			return false;
388 		}
389 
390 		/**
391 		 * Gets the next pending connection
392 		 *
393 		 * @return The socket of the connection
394 		 */
395 		public Socket getConnection() {
396 			try {
397 				return this.iConnectionPool.poll(100, TimeUnit.MILLISECONDS);
398 			} catch (InterruptedException e) {
399 				// just ignore it, it is expected
400 			}
401 			return null;
402 		}
403 
404 		@Override
405 		public void run() {
406 			while (this.iAlive) {
407 				try {
408 					Socket socket = getConnection();
409 					if (socket != null) {
410 						this.iThreadPool.execute(new HttpServerWorker(this.iHandler, socket));
411 					}
412 				} catch (Throwable t) {
413 					try {
414 						LogAndTraceBroker.getBroker().trace(Level.FINE, "Exception while submitting worker to thread pool", t);
415 					} catch (Throwable t1) {
416 						// forget it
417 					}
418 				}
419 			}
420 			try {
421 				this.iHandler.close();
422 			} catch (Exception e) {
423 				LogAndTraceBroker.getBroker().trace(Level.FINE, "Exception while closing http connection handler", e);
424 			}
425 			try {
426 				this.iThreadPool.shutdown();
427 			} catch (Exception e) {
428 				LogAndTraceBroker.getBroker().trace(Level.FINE, "Exception during shut down of thread pool", e);
429 			}
430 		}
431 
432 		/**
433 		 * Closes the dispatcher
434 		 */
435 		public void close() {
436 			this.iAlive = false;
437 		}
438 	}
439 }