Chapter 14. Server and Service

In previous chapters you have seen how you can have a servlet container by instantiating a connector and a container and then associating them with each other. Only one connector could be used, and that was to serve HTTP requests on port 8080. You could not add another connector to service HTTPS requests, for example.

In addition, all applications accompanying the previous chapters lack one thing: a good mechanism for starting and stopping the servlet container. In this chapter, we’ll look at two other components that offer this mechanism as well as offer many other features: server and service.

Server

The org.apache.catalina.Server interface represents the entire Catalina servlet container and engulfs all other components. A server is particularly useful because it provides an elegant mechanism for starting and stopping the whole system. There is no need to start the connector and the container individually any longer.

Here is how the start and stop mechanism works. When you start a server, it starts all components inside it. It then waits indefinitely for a shutdown command. If you want to shut the system down, you send a shutdown command to a designated port. This will reach the server and if it receives the right shutdown command, it will obey by stopping all its components.

A server uses another component, a service, to contain components such as a container and one or more connectors. Service is explained in the section “Service” later in this chapter.

The Server interface is given in Listing 14.1.

Example 14.1. The Server interface

package org.apache.catalina;
import org.apache.catalina.deploy.NamingResources;

public interface Server {
  /**
   * Return descriptive information about this Server implementation
   * and the corresponding version number, in the format
   * <code>&lt;description&gt;/&lt;version&gt;</code>.
   */
  public String getInfo();
  /**
   * Return the global naming resources.
   */
  public NamingResources getGlobalNamingResources();
  /**
   * Set the global naming resources.
   *
   * @param namingResources The new global naming resources
   */
  public void setGlobalNamingResources
    (NamingResources globalNamingResources);
  /**
   * Return the port number we listen to for shutdown commands.
   */
  public int getPort();
  /**
   * Set the port number we listen to for shutdown commands.
   *
   * @param port The new port number
   */
  public void setPort(int port);
  /**
   * Return the shutdown command string we are waiting for.
   */
  public String getShutdown();
  /**
   * Set the shutdown command we are waiting for.
   *
   * @param shutdown The new shutdown command
   */
  public void setShutdown(String shutdown);
  /**
   * Add a new Service to the set of defined Services.
   *
   * @param service The Service to be added
   */
  public void addService(Service service);
  /**
   * Wait until a proper shutdown command is received, then return.
   */
  public void await();
  /**
   * Return the specified Service (if it exists); otherwise return
   * <code>null</code>.
   *
   * @param name Name of the Service to be returned
   */
  public Service findService(String name);
  /**
   * Return the set of Services defined within this Server.
   */
  public Service[] findServices();
  /**
   * Remove the specified Service from the set associated from this
   * Server.
   *
   * @param service The Service to be removed
   */
  public void removeService(Service service);
  /**
   * Invoke a pre-startup initialization. This is used to allow
   * onnectors to bind to restricted ports under Unix operating
   * environments.
   *
   * @exception LifecycleException If this server was already
   * initialized.
   */
  public void initialize() throws LifecycleException;
}

The shutdown property holds the string that must be sent to a Server instance to shut it down. The port property defines the port the server waits for a shutdown command. You can add Service objects to a server by calling its addService method. Later, a service can be removed by invoking the removeService method. The findServices method returns all services added to the server. The initialize method contains code that needs to be executed before start-up.

StandardServer

The org.apache.catalina.core.StandardServer class is the standard implementation of Server. We’re particularly interested in the shutdown mechanism offered by this class, which is also the most important feature in this class. Many methods are related to the storing of the server configuration into a new server.xml file, but they will not be discussed here. For those interested, these methods are not difficult to understand, though.

A Server can have zero or more services. The StandardServer provides the implementations of the addService, removeService, and findServices methods.

Four methods are related to the lifecycle of the StandardServer: initialize, start, stop, and await. Just like any other components, you initialize and start a server. You then call the await method followed by the stop method. The await method does not return until it receives a shutdown command on port 8085 (or some other port). When the await method returns, the stop method is run to stop all the sub-components. In the accompanying application of this chapter, you will learn how you can implement this shutdown mechanism.

The initialize, start, stop, and await methods are discussed in the following sub-sections.

The initialize Method

The initialize method is used to initialize services added to the Server instance. The initialize method in the StandardServer class in Tomcat 4 is given in Listing 14.2.

Example 14.2. The initialize method

public void initialize() throws LifecycleException {
  if (initialized)
    throw new LifecycleException (
      sm.getString("standardServer.initialize.initialized"));
  initialized = true;

  // Initialize our defined Services
  for (int i = 0; i < services.length; i++) {
    services[i].initialize();
  }
}

Note that the initialize method employs a boolean named initialized that prevents the server from being initialized twice. In Tomcat 5, the initialize method is similar, but it also includes code related to JMX (discussed in Chapter 20). The stop method does not reset the value of initialized, so that if the server is stopped and started again, its initialized method is not called again.

The start Method

You call the start method to start a Server. The implementation of this method in StandardServer starts all services which in turn starts all other components, such as the connector(s) and the container. Listing 14.3 presents the start method.

Example 14.3. The start method

public void start() throws LifecycleException {
  // Validate and update our current component state
  if (started)
    throw new LifecycleException
      (sm.getString("standardServer.start.started"));
  // Notify our interested LifecycleListeners
  lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
  lifecycle.fireLifecycleEvent(START_EVENT, null);
  started = true;
  // Start our defined Services
  synchronized (services) {
    for (int i = 0; i < services.length; i++) {
      if (services[i] instanceof Lifecycle)
        ((Lifecycle) services[i]).start();
    }
  // Notify our interested LifecycleListeners
  lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}

The start method employs the start boolean variable to prevent a server from being started twice. The stop method resets this variable.

The stop Method

The stop method stops the server. This method is given in Listing 14.4.

Example 14.4. The stop method

public void stop() throws LifecycleException {
  // Validate and update our current component state
  if (!started)
    throw new LifecycleException
      (sm.getString("standardServer.stop.notStarted"));
  // Notify our interested LifecycleListeners
  lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
  lifecycle.fireLifecycleEvent(STOP_EVENT, null);
  started = false;

  // Stop our defined Services
  for (int i = 0; i < services.length; i++) {
    if (services[i] instanceof Lifecycle)
      ((Lifecycle) services[i]).stop();
  }
  // Notify our interested LifecycleListeners
  lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
}

Calling the stop method stops all the services and resets the started boolean, so that the server can be started again.

The await Method

The await method is responsible for the stop mechanism of the whole Tomcat deployment. It is given in Listing 14.5.

Example 14.5. The await method

/**
 * Wait until a proper shutdown command is received, then return.
 */
public void await() {
  // Set up a server socket to wait on
  ServerSocket serverSocket = null;
  try {
    serverSocket = new ServerSocket(port, 1,
      InetAddress.getByName("127.0.0.1"));
  }
  catch (IOException e) {
    System.err.println("StandardServer.await: create[" +
      port + "]: " + e);
    e.printStackTrace();
    System.exit(1);
  }
  // Loop waiting for a connection and a valid command
  while (true) {
    // Wait for the next connection
    Socket socket = null;
    InputStream stream = null;
    try {
      socket = serverSocket.accept();
      socket.setSoTimeout(10 * 1000);  // Ten seconds
      stream = socket.getInputStream();
    }
    catch (AccessControlException ace) {
      System.err.println("StandardServer.accept security exception: "
        + ace.getMessage());
      continue;
    }
    catch (IOException e) {
      System.err.println("StandardServer.await: accept: " + e);
      e.printStackTrace();
      System.exit(1);
    }

    // Read a set of characters from the socket
    StringBuffer command = new StringBuffer();
    int expected = 1024; // Cut off to avoid DoS attack
    while (expected < shutdown.length()) {
      if (random == null)
        random = new Random(System.currentTimeMillis());
      expected += (random.nextInt() % 1024);
    }
    while (expected > 0) {
      int ch = -1;
      try {
         ch = stream.read();
      }
      catch (IOException e) {
        System.err.println("StandardServer.await: read: " + e);
        e.printStackTrace();
        ch = -1;
      }
      if (ch < 32)  // Control character or EOF terminates loop
        break;
      command.append((char) ch);
      expected--;
    }

    // Close the socket now that we are done with it
    try {
      socket.close();
    }
    catch (IOException e) {
      ;
    }

    // Match against our command string
    boolean match = command.toString().equals(shutdown);
    if (match) {
      break;
    }
    else
      System.err.println("StandardServer.await: Invalid command '" +
        command.toString() + "' received");
  }

  // Close the server socket and return
  try {
    serverSocket.close();
  }
  catch (IOException e) {
    ;
  }
}

The await method creates a ServerSocket object that waits on port 8085 and then calls its accept method in a while loop. The accept method will only return if there is an incoming message on the designated port (8085). Each message will then be matched against the shutdown string. If it matches, control breaks out of the while loop and the SocketServer is closed. If it does not match, control stays in the while loop waiting for another message.

Service

The org.apache.catalina.Service interface represents a service. A service can hold one container and multiple connectors. You can add as many connectors as you want and all the connectors will be associated with the container. The Service interface is given in Listing 14.6.

Example 14.6. The Service interface

package org.apache.catalina;

public interface Service {

  /**
   * Return the <code>Container</code> that handles requests for all
   * <code>Connectors</code> associated with this Service.
   */
  public Container getContainer();

  /**
   * Set the <code>Container</code> that handles requests for all
   * <code>Connectors</code> associated with this Service.
   *
   * @param container The new Container
   */
  public void setContainer(Container container);

  /**
   * Return descriptive information about this Service implementation
   * and the corresponding version number, in the format
   * <code>&lt;description&gt;/&lt;version&gt;</code>.
   */
  public String getInfo();

  /**
   * Return the name of this Service.
   */
  public String getName();

  /**
   * Set the name of this Service.
   *
   * @param name The new service name
   */
  public void setName(String name);

  /**
   * Return the <code>Server</code> with which we are associated
   * (if any).
   */
  public Server getServer();

  /**
   * Set the <code>Server</code> with which we are associated (if any).
   *
   * @param server The server that owns this Service
   */
  public void setServer(Server server);

  /**
   * Add a new Connector to the set of defined Connectors,
   * and associate it with this Service's Container.
   *
   * @param connector The Connector to be added
   */
  public void addConnector(Connector connector);

  /**
   * Find and return the set of Connectors associated with
   * this Service.
   */
  public Connector[] findConnectors();

  /**
   * Remove the specified Connector from the set associated from this
   * Service.  The removed Connector will also be disassociated
   * from our Container.
   *
   * @param connector The Connector to be removed
   */
  public void removeConnector(Connector connector);

  /**
   * Invoke a pre-startup initialization. This is used to
   * allow connectors to bind to restricted ports under
   * Unix operating environments.
   *
   * @exception LifecycleException If this server was
   * already initialized.
   */
  public void initialize() throws LifecycleException;
}

StandardService

The org.apache.catalina.core.StandardService class is the standard implementation of Service. The StandardService class’s initialize method initializes all the connectors added to the service. StandardService implements Service as well as the org.apache.catalina.Lifecycle interface. Its start method starts the container and all the connectors.

Container and Connector(s)

A StandardService instance contains two types of components: a container and one or more connectors. Being able to have multiple connectors enable Tomcat to service multiple protocols. One connector can be used to service HTTP requests, and another for servicing HTTPS requests.

The StandardService class uses the container variable as an object reference for the the Container instance and the connectors array for all the connectors.

private Container container = null;
private Connector connectors[] = new Connector[0];

To associate a container with this service, you use the setContainer method, which is given in Listing 14.7.

Example 14.7. The setContainer method

public void setContainer(Container container) {
  Container oldContainer = this.container;
  if ((oldContainer != null) && (oldContainer instanceof Engine))
    ((Engine) oldContainer).setService(null);
  this.container = container;
  if ((this.container != null) && (this.container instanceof Engine))
   ((Engine) this.container).setService(this);
  if (started && (this.container != null) &&
    (this.container instanceof Lifecycle)) {
     try {
       ((Lifecycle) this.container).start();
     }
     catch (LifecycleException e) {
       ;
     }
  }
  synchronized (connectors) {
    for (int i = 0; i < connectors.length; i++)
      connectors[i].setContainer(this.container);
  }
  if (started && (oldContainer != null) &&
    (oldContainer instanceof Lifecycle)) {
    try {
      ((Lifecycle) oldContainer).stop();
    }
    catch (LifecycleException e) {
      ;
    }
  }

  // Report this property change to interested listeners
  support.firePropertyChange("container", oldContainer,
    this.container);
}

The container associated with this service will be passed to the setContainer method on each Connector object that is available in this service to create the association between the container and each individual connector.

To add a connector to a Service object, use the addConnector method. To remove a connector, call the removeConnector method. The addConnector method is given in Listing 14.8 and the removeConnector method in Listing 14.9.

Example 14.8. The addConnector method

public void addConnector(Connector connector) {
  synchronized (connectors) {
    connector.setContainer(this.container);
    connector.setService(this);
    Connector results[] = new Connector[connectors.length + 1];
    System.arraycopy(connectors, 0, results, 0, connectors.length);
    results[connectors.length] = connector;
    connectors = results;

    if (initialized) {
      try {
        connector.initialize();
      }
    catch (LifecycleException e) {
      e.printStackTrace(System.err);
    }
  }

  if (started && (connector instanceof Lifecycle)) {
    try {
      ((Lifecycle) connector).start();
    }
    catch (LifecycleException e) {
      ;
    }
  }

  // Report this property change to interested listeners
  support.firePropertyChange("connector", null, connector);
  }
}

The addConnector method initializes and starts the added connector.

Example 14.9. The removeConnector method

public void removeConnector(Connector connector) {
  synchronized (connectors) {
  int j = -1;
  for (int i = 0; i < connectors.length; i++) {
    if (connector == connectors[i]) {
      j = i;
      break;
    }
  }
  if (j < 0)
    return;
  if (started && (connectors[j] instanceof Lifecycle)) {
    try {
      ((Lifecycle) connectors[j]).stop();
    }
    catch (LifecycleException e) {
      ;
    }
  }
  connectors[j].setContainer(null);
  connector.setService(null);
  int k = 0;
  Connector results[] = new Connector[connectors.length - 1];
  for (int i = 0; i < connectors.length; i++) {
    if (i != j)
      results[k++] = connectors[i];
  }
  connectors = results;

  // Report this property change to interested listeners
  support.firePropertyChange("connector", connector, null);
  }
}

Lifecycle Methods

The lifecycle methods are the start and the stop methods inherited from the Lifecycle interface plus the initialize method. The initialize method calls the initialize method of each connector in this service. This method is given in Listing 14.10.

Example 14.10. The initialize method of StandardService

public void initialize() throws LifecycleException {
  if (initialized)
    throw new LifecycleException (
      sm.getString("standardService.initialize.initialized"));
  initialized = true;

  // Initialize our defined Connectors
  synchronized (connectors) {
    for (int i = 0; i < connectors.length; i++) {
      connectors[i].initialize();
    }
  }
}

The start method starts the associated container as well as all the connector added to this service. This method is presented in Listing 14.11.

Example 14.11. The start method

public void start() throws LifecycleException {
  // Validate and update our current component state
  if (started) {
    throw new LifecycleException
      (sm.getString("standardService.start.started"));
  }

  // Notify our interested LifecycleListeners
  lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);

  System.out.println
    (sm.getString("standardService.start.name", this.name));
  lifecycle.fireLifecycleEvent(START_EVENT, null);
  started = true;

  // Start our defined Container first
  if (container != null) {
    synchronized (container) {
      if (container instanceof Lifecycle) {
        ((Lifecycle) container).start();
      }
    }
  }

  // Start our defined Connectors second
  synchronized (connectors) {
    for (int i = 0; i < connectors.length; i++) {
      if (connectors[i] instanceof Lifecycle)
        ((Lifecycle) connectors[i]).start();
    }
  }

  // Notify our interested LifecycleListeners
  lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}

The stop method stops the associated container and all the connectors. The stop method is given in Listing 14.12.

Example 14.12. The stop method

public void stop() throws LifecycleException {
  // Validate and update our current component state
  if (!started) {
    throw new LifecycleException
      (sm.getString("standardService.stop.notStarted"));
  }

  // Notify our interested LifecycleListeners
  lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);

  lifecycle.fireLifecycleEvent(STOP_EVENT, null);

  System.out.println
    (sm.getString("standardService.stop.name", this.name));
  started = false;

  // Stop our defined Connectors first
  synchronized (connectors) {
    for (int i = 0; i < connectors.length; i++) {
      if (connectors[i] instanceof Lifecycle)
        ((Lifecycle) connectors[i]).stop();
    }
  }

  // Stop our defined Container second
  if (container != null) {
    synchronized (container) {
      if (container instanceof Lifecycle) {
        ((Lifecycle) container).stop();
      }
    }
  }

  // Notify our interested LifecycleListeners
  lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
}

The Application

The application shows you how to use a server and a service. In particular, it demonstrates how to utilize the start and stop mechanisms in the StandardServer class. There are three classes in the application. The first is the SimpleContextConfig, which is a copy from the application in Chapter 13. The two other classes are Bootstrap and Stopper. The Bootstrap class is used to start the application, and the Stopper class to stop it.

The Bootstrap Class

The Bootstrap class is given in Listing 14.13.

Example 14.13. The Bootstrap Class

package ex14.pyrmont.startup;

import ex14.pyrmont.core.SimpleContextConfig;
import org.apache.catalina.Connector;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardServer;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.loader.WebappLoader;

public final class Bootstrap {
  public static void main(String[] args) {
    System.setProperty("catalina.base",
      System.getProperty("user.dir"));
    Connector connector = new HttpConnector();
    Wrapper wrapper1 = new StandardWrapper();
    wrapper1.setName("Primitive");
    wrapper1.setServletClass("PrimitiveServlet");
    Wrapper wrapper2 = new StandardWrapper();
    wrapper2.setName("Modern");
    wrapper2.setServletClass("ModernServlet");
    Context context = new StandardContext();
    // StandardContext's start method adds a default mapper
    context.setPath("/app1");
    context.setDocBase("app1");
    context.addChild(wrapper1);
    context.addChild(wrapper2);
    LifecycleListener listener = new SimpleContextConfig();
    ((Lifecycle) context).addLifecycleListener(listener);
    Host host = new StandardHost();
    host.addChild(context);
    host.setName("localhost");
    host.setAppBase("webapps");
    Loader loader = new WebappLoader();
    context.setLoader(loader);
    // context.addServletMapping(pattern, name);
    context.addServletMapping("/Primitive", "Primitive");
    context.addServletMapping("/Modern", "Modern");
    Engine engine = new StandardEngine();
    engine.addChild(host);
    engine.setDefaultHost("localhost");
    Service service = new StandardService();
    service.setName("Stand-alone Service");
    Server server = new StandardServer();
    server.addService(service);
    service.addConnector(connector);
    // StandardService class's setContainer method calls
    // its connectors' setContainer method
    service.setContainer(engine);
    // Start the new server
    if (server instanceof Lifecycle) {
      try {
        server.initialize();
        ((Lifecycle) server).start();
        server.await();
        // the program waits until the await method returns,
        // i.e. until a shutdown command is received.
      }
      catch (LifecycleException e) {
        e.printStackTrace(System.out);
      }
    }

    // Shut down the server
    if (server instanceof Lifecycle) {
      try {
        ((Lifecycle) server).stop();
      }
      catch (LifecycleException e) {
        e.printStackTrace(System.out);
      }
    }
  }
}

The beginning part of the main method of the Bootstrap class is similar to the one in Chapter 13. It creates a connector, two wrappers, a context, a host, and an engine. It then adds the wrappers to the context, the context to the host, and the host to the engine. It does not associate the connector with the top level container (the engine), however. Instead, the main method creates a Service object, sets its name, creates a Server object, and adds the service to the server:

Service service = new StandardService();
service.setName("Stand-alone Service");
Server server = new StandardServer();
server.addService(service);

The main method then adds the connector and the engine to the service:

service.addConnector(connector);
service.setContainer(engine);

By adding the connector to the service, the connector will be associated with the container in the service.

The main method then calls the initialize and start methods of the Server, thereby initializing the connector and starting the connector and the container:

if (server instanceof Lifecycle) {
  try {
    server.initialize();
    ((Lifecycle) server).start();

Next, it calls the server’s await method that makes the server waits for a shutdown command on port 8085. Note that at this stage the connector is already running, waiting for HTTP requests on port 8080 (the default port).

server.await();

The await method will not return until the correct shutdown command is received. When that happens, the main method calls the server’s stop method, in effect stopping all other components.

Let’s now review the Stopper class that you can use to stop the Server.

The Stopper Class

In the application in previous chapters, you either stop the container either abruptly or by pressing a key. The Stopper class provides a more elegant way of stopping a Catalina server. It also makes sure that the stop method of all lifecycle components will be called.

The Stopper class is given in Listing 14.14.

Example 14.14. The Stopper class

package ex14.pyrmont.startup;

import java.io.OutputStream;
import java.io.IOException;
import java.net.Socket;

public class Stopper {
  public static void main(String[] args) {
    // the following code is taken from the Stop method of
    // the org.apache.catalina.startup.Catalina class
    int port = 8005;
    try {
      Socket socket = new Socket("127.0.0.1", port);
      OutputStream stream = socket.getOutputStream();
      String shutdown = "SHUTDOWN";
      for (int i = 0; i < shutdown.length(); i++)
        stream.write(shutdown.charAt(i));
      stream.flush();
      stream.close();
      socket.close();
      System.out.println("The server was successfully shut down.");
    }
    catch (IOException e) {
      System.out.println("Error. The server has not been started.");
    }
  }
}

The main method of the Stopper class creates a Socket object and then flushes the string SHUTDOWN, which is the correct shutdown command, to port 8085. If a Catalina server is running, it will be shut down.

Running the Applications

To run the Bootstrap class in Windows, from the working directory, type the following:

java -classpath ./lib/servlet.jar;./lib/commons-
collections.jar;./lib/commons-digester.jar;./lib/naming-
factory.jar;./lib/naming-common.jar;./ ex14.pyrmont.startup.Bootstrap

In Linux, you use a colon to separate two libraries.

java -classpath ./lib/servlet.jar:./lib/commons-
collections.jar:./lib/commons-digester.jar:./lib/naming-
factory.jar:./lib/naming-common.jar:./ ex14.pyrmont.startup.Bootstrap

To invoke PrimitiveServlet, use the following URL in your browser.

http://localhost:8080/app1/Primitive

To invoke ModernServlet, use the following URL.

http://localhost:8080/app1/Modern

To run the stopper to stop the application in both Windows and Linux, from the working directory, type the following:

java ex14.pyrmont.startup.Stopper

Note that in a real Catalina deployment, the functionality offered by the Stopper class is encapsulated into the Bootstrap class.

Summary

This chapter explains two important components in Catalina: server and service. A server is particularly useful because it provides a gentle mechanism for starting and stopping a Catalina deployment. A service component encapsulates a container and one or many connectors. The application accompanying this chapter shows how to use the server and service components. It also demonstrates how you can use the stop mechanism in the StandardServer class.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.111.9