Coverage Report - org.apache.xmlrpc.webserver.WebServer
 
Classes in this File Line Coverage Branch Coverage Complexity
WebServer
34% 
33% 
3,053
 
 1  
 /*
 2  
  * Copyright 1999,2005 The Apache Software Foundation.
 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  
  * 
 8  
  *      http://www.apache.org/licenses/LICENSE-2.0
 9  
  * 
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package org.apache.xmlrpc.webserver;
 17  
 
 18  
 import java.io.IOException;
 19  
 import java.io.InterruptedIOException;
 20  
 import java.net.BindException;
 21  
 import java.net.InetAddress;
 22  
 import java.net.ServerSocket;
 23  
 import java.net.Socket;
 24  
 import java.net.SocketException;
 25  
 import java.util.ArrayList;
 26  
 import java.util.List;
 27  
 import java.util.StringTokenizer;
 28  
 
 29  
 import org.apache.commons.logging.Log;
 30  
 import org.apache.commons.logging.LogFactory;
 31  
 import org.apache.xmlrpc.server.XmlRpcStreamServer;
 32  
 import org.apache.xmlrpc.util.ThreadPool;
 33  
 
 34  
 
 35  
 /** A minimal web server that exclusively handles XML-RPC requests.
 36  
  */
 37  1
 public class WebServer implements Runnable {
 38  1
         private static final Log log = LogFactory.getLog(WebServer.class);
 39  
 
 40  
         private class AddressMatcher {
 41  
                 private final int pattern[];
 42  
                 
 43  0
                 AddressMatcher(String address) {
 44  
                         try {
 45  0
                                 pattern = new int[4];
 46  0
                                 StringTokenizer st = new StringTokenizer(address, ".");
 47  0
                                 if (st.countTokens() != 4) {
 48  0
                                         throw new IllegalArgumentException();
 49  
                                 }
 50  0
                                 for (int i = 0; i < 4; i++)        {
 51  0
                                         String next = st.nextToken();
 52  0
                                         if ("*".equals(next)) {
 53  0
                                                 pattern[i] = 256;
 54  
                                         } else {
 55  0
                                                 pattern[i] = Integer.parseInt(next);
 56  
                                         }
 57  
                                 }
 58  0
                         } catch (Exception e) {
 59  0
                                 throw new IllegalArgumentException("\"" + address
 60  0
                                                 + "\" does not represent a valid IP address");
 61  
                         }
 62  0
                 }
 63  
                 
 64  
                 boolean matches(byte[] pAddress) {
 65  0
                         for (int i = 0; i < 4; i++)        {
 66  0
                                 if (pattern[i] > 255) {
 67  0
                                         continue; // Wildcard
 68  
                                 }
 69  0
                                 if (pattern[i] != pAddress[i]) {
 70  0
                                         return false;
 71  
                                 }
 72  
                         }
 73  0
                         return true;
 74  
                 }
 75  
         }
 76  
 
 77  
         protected ServerSocket serverSocket;
 78  
         private Thread listener;
 79  
         private ThreadPool pool;
 80  154
         protected final List accept = new ArrayList();
 81  154
         protected final List deny = new ArrayList();
 82  154
         protected final XmlRpcStreamServer server = newXmlRpcStreamServer();
 83  
 
 84  
         protected XmlRpcStreamServer newXmlRpcStreamServer(){
 85  154
                 return new ConnectionServer();
 86  
         }
 87  
 
 88  
         // Inputs to setupServerSocket()
 89  
         private InetAddress address;
 90  
         private int port;
 91  
         
 92  
         private boolean paranoid;
 93  
         
 94  
         static final String HTTP_11 = "HTTP/1.1";
 95  
         /** Creates a web server at the specified port number.
 96  
          * @param pPort Port number; 0 for a random port, choosen by the
 97  
          * operating system.
 98  
          */
 99  
         public WebServer(int pPort) {
 100  110
                 this(pPort, null);
 101  110
         }
 102  
         
 103  
         /** Creates a web server at the specified port number and IP address.
 104  
          * @param pPort Port number; 0 for a random port, choosen by the
 105  
          * operating system.
 106  
          * @param pAddr Local IP address; null for all available IP addresses.
 107  
          */
 108  154
         public WebServer(int pPort, InetAddress pAddr) {
 109  154
                 address = pAddr;
 110  154
                 port = pPort;
 111  154
         }
 112  
         
 113  
         /**
 114  
          * Factory method to manufacture the server socket.  Useful as a
 115  
          * hook method for subclasses to override when they desire
 116  
          * different flavor of socket (i.e. a <code>SSLServerSocket</code>).
 117  
          *
 118  
          * @param pPort Port number; 0 for a random port, choosen by the operating
 119  
          * system.
 120  
          * @param backlog
 121  
          * @param addr If <code>null</code>, binds to
 122  
          * <code>INADDR_ANY</code>, meaning that all network interfaces on
 123  
          * a multi-homed host will be listening.
 124  
          * @exception IOException Error creating listener socket.
 125  
          */
 126  
         protected ServerSocket createServerSocket(int pPort, int backlog, InetAddress addr)
 127  
                         throws IOException {
 128  154
                 return new ServerSocket(pPort, backlog, addr);
 129  
         }
 130  
         
 131  
         /**
 132  
          * Initializes this server's listener socket with the specified
 133  
          * attributes, assuring that a socket timeout has been set.  The
 134  
          * {@link #createServerSocket(int, int, InetAddress)} method can
 135  
          * be overridden to change the flavor of socket used.
 136  
          *
 137  
          * @see #createServerSocket(int, int, InetAddress)
 138  
          */
 139  
         private synchronized void setupServerSocket(int backlog) throws IOException {
 140  
                 // Since we can't reliably set SO_REUSEADDR until JDK 1.4 is
 141  
                 // the standard, try to (re-)open the server socket several
 142  
                 // times.  Some OSes (Linux and Solaris, for example), hold on
 143  
                 // to listener sockets for a brief period of time for security
 144  
                 // reasons before relinquishing their hold.
 145  154
                 for (int i = 1;  ;  i++) {
 146  
                         try {
 147  154
                                 serverSocket = createServerSocket(port, backlog, address);
 148  
                                 // A socket timeout must be set.
 149  154
                                 if (serverSocket.getSoTimeout() <= 0) {
 150  154
                                         serverSocket.setSoTimeout(4096);
 151  
                                 }
 152  154
                                 return;
 153  0
                         } catch (BindException e) {
 154  0
                                 if (i == 10) {
 155  0
                                         throw e;
 156  
                                 } else {
 157  0
                                         long waitUntil = System.currentTimeMillis();
 158  0
                                         for (;;) {
 159  0
                                                 long l = waitUntil - System.currentTimeMillis();
 160  0
                                                 if (l > 0) {
 161  
                                                         try {
 162  0
                                                                 Thread.sleep(l);
 163  0
                                                         } catch (InterruptedException ex) {
 164  
                                                         }
 165  
                                                 }
 166  
                                         }
 167  
                                 }
 168  
                         }
 169  
                 }
 170  
         }
 171  
         
 172  
         /**
 173  
          * Spawns a new thread which binds this server to the port it's
 174  
          * configured to accept connections on.
 175  
          *
 176  
          * @see #run()
 177  
          * @throws IOException Binding the server socket failed.
 178  
          */
 179  
         public void start() throws IOException {
 180  154
                 setupServerSocket(50);
 181  
                 
 182  
                 // The listener reference is released upon shutdown().
 183  154
                 if (listener == null) {
 184  154
                         listener = new Thread(this, "XML-RPC Weblistener");
 185  
                         // Not marked as daemon thread since run directly via main().
 186  154
                         listener.start();
 187  
                 }
 188  154
         }
 189  
         
 190  
         /**
 191  
          * Switch client filtering on/off.
 192  
          * @param pParanoid True to enable filtering, false otherwise.
 193  
          * @see #acceptClient(java.lang.String)
 194  
          * @see #denyClient(java.lang.String)
 195  
          */
 196  
         public void setParanoid(boolean pParanoid) {
 197  0
                 paranoid = pParanoid;
 198  0
         }
 199  
         
 200  
         /** Add an IP address to the list of accepted clients. The parameter can
 201  
          * contain '*' as wildcard character, e.g. "192.168.*.*". You must call
 202  
          * setParanoid(true) in order for this to have any effect.
 203  
          * @param pAddress The IP address being enabled.
 204  
          * @see #denyClient(java.lang.String)
 205  
          * @see #setParanoid(boolean)
 206  
          * @throws IllegalArgumentException Parsing the address failed.
 207  
          */
 208  
         public void acceptClient(String pAddress) {
 209  0
                 accept.add(new AddressMatcher(pAddress));
 210  0
         }
 211  
         
 212  
         /**
 213  
          * Add an IP address to the list of denied clients. The parameter can
 214  
          * contain '*' as wildcard character, e.g. "192.168.*.*". You must call
 215  
          * setParanoid(true) in order for this to have any effect.
 216  
          * @param pAddress The IP address being disabled.
 217  
          * @see #acceptClient(java.lang.String)
 218  
          * @see #setParanoid(boolean)
 219  
          * @throws IllegalArgumentException Parsing the address failed.
 220  
          */
 221  
         public void denyClient(String pAddress) {
 222  0
                 deny.add(new AddressMatcher(pAddress));
 223  0
         }
 224  
         
 225  
         /**
 226  
          * Checks incoming connections to see if they should be allowed.
 227  
          * If not in paranoid mode, always returns true.
 228  
          *
 229  
          * @param s The socket to inspect.
 230  
          * @return Whether the connection should be allowed.
 231  
          */
 232  
         protected boolean allowConnection(Socket s) {
 233  245
                 if (!paranoid) {
 234  245
                         return true;
 235  
                 }
 236  
                 
 237  0
                 int l = deny.size();
 238  0
                 byte addr[] = s.getInetAddress().getAddress();
 239  0
                 for (int i = 0; i < l; i++) {
 240  0
                         AddressMatcher match = (AddressMatcher) deny.get(i);
 241  0
                         if (match.matches(addr))
 242  
                         {
 243  0
                                 return false;
 244  
                         }
 245  
                 }
 246  0
                 l = accept.size();
 247  0
                 for (int i = 0; i < l; i++) {
 248  0
                         AddressMatcher match = (AddressMatcher) accept.get(i);
 249  0
                         if (match.matches(addr)) {
 250  0
                                 return true;
 251  
                         }
 252  
                 }
 253  0
                 return false;
 254  
         }
 255  
 
 256  
         protected ThreadPool.Task newTask(WebServer pServer, XmlRpcStreamServer pXmlRpcServer,
 257  
                                                                              Socket pSocket) throws IOException {
 258  175
                 return new Connection(pServer, pXmlRpcServer, pSocket);
 259  
         }
 260  
 
 261  
         /**
 262  
          * Listens for client requests until stopped.  Call {@link
 263  
          * #start()} to invoke this method, and {@link #shutdown()} to
 264  
          * break out of it.
 265  
          *
 266  
          * @throws RuntimeException Generally caused by either an
 267  
          * <code>UnknownHostException</code> or <code>BindException</code>
 268  
          * with the vanilla web server.
 269  
          *
 270  
          * @see #start()
 271  
          * @see #shutdown()
 272  
          */
 273  
         public void run() {
 274  154
                 pool = new ThreadPool(server.getMaxThreads(), "XML-RPC");
 275  
                 try {
 276  585
                         while (listener != null) {
 277  
                                 try {
 278  431
                                         Socket socket = serverSocket.accept();
 279  
                                         try {
 280  245
                                                 socket.setTcpNoDelay(true);
 281  0
                                         } catch (SocketException socketOptEx) {
 282  0
                                                 log(socketOptEx);
 283  
                                         }
 284  
                                         
 285  
                                         try {
 286  245
                                                 if (allowConnection(socket)) {
 287  245
                                                         final ThreadPool.Task task = newTask(this, server, socket);
 288  245
                                                         if (pool.startTask(task)) {
 289  245
                                                                 socket = null;
 290  
                                                         } else {
 291  0
                                                                 log("Maximum load of " + pool.getMaxThreads()
 292  0
                                                                         + " exceeded, rejecting client");
 293  
                                                         }
 294  
                                                 }
 295  0
                                         } finally {
 296  245
                                                 if (socket != null) { try { socket.close(); } catch (Throwable ignore) {} }
 297  0
                                         }
 298  32
                                 } catch (InterruptedIOException checkState) {
 299  
                                         // Timeout while waiting for a client (from
 300  
                                         // SO_TIMEOUT)...try again if still listening.
 301  0
                                 } catch (Throwable t) {
 302  0
                                         log(t);
 303  
                                 }
 304  
                         }
 305  0
                 } finally {
 306  0
                         if (serverSocket != null) {
 307  
                                 try {
 308  0
                                         serverSocket.close();
 309  0
                                 } catch (IOException e) {
 310  0
                                         log(e);
 311  
                                 }
 312  
                         }
 313  
                         
 314  
                         // Shutdown our Runner-based threads
 315  0
                         pool.shutdown();
 316  0
                 }
 317  0
         }
 318  
         
 319  
         /**
 320  
          * Stop listening on the server port.  Shutting down our {@link
 321  
          * #listener} effectively breaks it out of its {@link #run()}
 322  
          * loop.
 323  
          *
 324  
          * @see #run()
 325  
          */
 326  
         public synchronized void shutdown() {
 327  
                 // Stop accepting client connections
 328  0
                 if (listener != null) {
 329  0
                         Thread l = listener;
 330  0
                         listener = null;
 331  0
                         l.interrupt();
 332  0
                         pool.shutdown();
 333  
                 }
 334  0
         }
 335  
         
 336  
         /** Returns the port, on which the web server is running.
 337  
          * This method may be invoked after {@link #start()} only.
 338  
          * @return Servers port number
 339  
          */
 340  284
         public int getPort() { return serverSocket.getLocalPort(); }
 341  
 
 342  
         /** Logs an error.
 343  
          * @param pError The error being logged.
 344  
          */
 345  
         public void log(Throwable pError) {
 346  0
                 log.error(pError.getMessage(), pError);
 347  0
         }
 348  
 
 349  
         /** Logs a message.
 350  
          * @param pMessage The being logged.
 351  
          */
 352  
         public synchronized void log(String pMessage) {
 353  0
                 log.error(pMessage);
 354  0
         }
 355  
 
 356  
         /** Returns the {@link org.apache.xmlrpc.server.XmlRpcServer}.
 357  
          * @return The server object.
 358  
          */
 359  
         public XmlRpcStreamServer getXmlRpcServer() {
 360  110
                 return server;
 361  
         }
 362  
 }