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.
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><description>/<version></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.
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 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.
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 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 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.
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><description>/<version></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; }
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.
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); } }
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 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 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.
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.
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.
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.
18.119.111.9