4.2 The Server

Java Servlet Programming, 2nd Edition > 4. Retrieving Information > 4.2 The Server

 
< BACKCONTINUE >

4.2 The Server

A servlet can find out much about the server in which it is executing. It can learn the hostname, listening port, and server software, among other things. A servlet can display this information to a client, use it to customize its behavior based on a particular server package, or even use it to explicitly restrict the machines on which the servlet will run.

4.2.1 Getting Information About the Server

A servlet gains most of its access to server information through the ServletContext object in which it executes. Before API 2.2, the ServletContext was generally thought of as a reference to the server itself. Since API 2.2 the rules have changed and there now must be a different ServletContext for each web application on the server. The ServletContext has become a reference to the web application, not a reference to the server. For simple server queries, there's not much difference.

There are five methods that a servlet can use to learn about its server: two that are called using the ServletRequest object passed to the servlet and three that are called from the ServletContext object in which the servlet is executing.

A servlet can get the name of the server and the port number for a particular request with getServerName( ) and getServerPort( ), respectively:

public String ServletRequest.getServerName() public int ServletRequest.getServerPort()

These methods are attributes of ServletRequest because the values can change for different requests if the server has more than one name (a technique called virtual hosting). The returned name might be something like www.servlets.com while the returned port might be something like 8080.

The getServerInfo( ) and getAttribute( ) methods of ServletContext provide information about the server software and its attributes:

public String ServletContext.getServerInfo() public Object ServletContext.getAttribute(String name)

getServerInfo( ) returns the name and version of the server software, separated by a slash. The string returned might be something like Tomcat Web Server/3.2. Some servers add extra information at the end describing the server operating environment.

getAttribute( ) returns the value of the named server attribute as an Object or null if the attribute does not exist. Servers have the option to place hardcoded attributes in the context for use by servlets. You can think of this method as a back door through which a servlet can get extra information about its server. For example, a server could make available statistics on server load, a reference to a shared resource pool, or any other potentially useful information. The only mandatory attribute a server must make available is an attribute named javax.servlet.context.tempdir, which provides a java.io.File reference to a directory private to this context.

Servlets can also add their own attributes to the context using the setAttribute( ) method as discussed in Chapter 11. Attribute names should follow the same convention as package names. The package names java.* and javax.* are reserved for use by the Java Software division of Sun Microsystems, and com.sun.* is reserved for use by Sun Microsystems. You can see your server's documentation for a list of its attributes. A listing of all current attributes stored by the server and other servlets can be obtained using getAttributeNames( ) :

public Enumeration ServletContext.getAttributeNames()

Because these methods are attributes of the ServletContext in which the servlet is executing, you have to call them through that object:

String serverInfo = getServletContext().getServerInfo();

The most straightforward use of information about the server is an "About This Server" servlet, as shown in Example 4-3.

Example 4-3. Snooping the Server
import java.io.*; import java.util.*; import javax.servlet.*; public class ServerSnoop extends GenericServlet {   public void service(ServletRequest req, ServletResponse res)                              throws ServletException, IOException {     res.setContentType("text/plain");     PrintWriter out = res.getWriter();     ServletContext context = getServletContext();     out.println("req.getServerName(): " + req.getServerName());     out.println("req.getServerPort(): " + req.getServerPort());     out.println("context.getServerInfo(): " + context.getServerInfo());     out.println("getServerInfo() name: " +                  getServerInfoName(context.getServerInfo()));     out.println("getServerInfo() version: " +                  getServerInfoVersion(context.getServerInfo()));     out.println("context.getAttributeNames():");     Enumeration enum = context.getAttributeNames();     while (enum.hasMoreElements()) {       String name = (String) enum.nextElement();       out.println("  context.getAttribute(\"" + name + "\"): " +                      context.getAttribute(name));     }   }   private String getServerInfoName(String serverInfo) {     int slash = serverInfo.indexOf('/');     if (slash == -1) return serverInfo;     else return serverInfo.substring(0, slash);   }   private String getServerInfoVersion(String serverInfo) {     // Version info is everything between the slash and the space     int slash = serverInfo.indexOf('/');     if (slash == -1) return null;     int space = serverInfo.indexOf(' ', slash);     if (space == -1) space = serverInfo.length();     return serverInfo.substring(slash + 1, space);   } }

This servlet also directly subclasses GenericServlet, demonstrating that all the information about a server is available to servlets of any type. The servlet outputs simple raw text. When accessed, this servlet prints something like:

req.getServerName(): localhost req.getServerPort(): 8080 context.getServerInfo(): Tomcat Web Server/3.2 (JSP 1.1; Servlet 2.2; ...) getServerInfo() name: Tomcat Web Server getServerInfo() version: 3.2 context.getAttributeNames():   context.getAttribute("javax.servlet.context.tempdir"): work/localhost_8080

4.2.2 Writing to a Temporary File

The javax.servlet.context.tempdir attribute maps to a temporary directory where short-lived working files can be stored. Each context receives a different temporary directory. For the previous example, the directory is server_root/work/localhost_8080. Example 4-4 shows how to write to a temporary file in the temporary directory.

Example 4-4. Creating a Temporary File in a Temporary Directory
public void doGet(HttpServletRequest req, HttpServletResponse res)                       throws ServletException, IOException {   // The directory is given as a File object   File dir = (File) getServletContext()                     .getAttribute("javax.servlet.context.tempdir");   // Construct a temp file in the temp dir (JDK 1.2 method)   File f = File.createTempFile("xxx", ".tmp", dir);   // Prepare to write to the file   FileOutputStream fout = new FileOutputStream(f);   // ... }

First, this servlet locates its temporary directory. Then, it uses the createTempFile( ) method to create a temporary file in that directory with an xxx prefix and .tmp suffix. Finally, it constructs a FileOutputStream to write to the temporary file. Files that must persist between server restarts should not be placed in the temporary directory.

4.2.3 Locking a Servlet to a Server

There are many ways to put this server information to productive use. Let's assume you've written a servlet and you don't want it running just anywhere. Perhaps you want to sell it and, to limit the chance of unauthorized copying, you want to lock the servlet to your customer's machine with a software license. Or, alternatively, you've written a license generator as a servlet and want to make sure it works only behind your firewall. This can be done relatively easily because a servlet has instant access to the information about its server.

Example 4-5 shows a servlet that locks itself to a particular server IP address and port number. It requires an init parameter key that is appropriate for its server IP address and port before it unlocks itself and handles a request. If it does not receive the appropriate key, it refuses to continue. The algorithm used to map the key to the IP address and port (and vice versa) must be secure.

Example 4-5. A Servlet Locked to a Server
import java.io.*; import java.net.*; import java.util.*; import javax.servlet.*; public class KeyedServerLock extends GenericServlet {   // This servlet has no class or instance variables   // associated with the locking, so as to simplify   // synchronization issues.   public void service(ServletRequest req, ServletResponse res)                              throws ServletException, IOException {     res.setContentType("text/plain");     PrintWriter out = res.getWriter();     // The piracy check shouldn't be done in init     // because name/port are part of request.     String key = getInitParameter("key");     String host = req.getServerName();     int port = req.getServerPort();     // Check if the init parameter "key" unlocks this server.     if (! keyFitsServer(key, host, port)) {       // Explain, condemn, threaten, etc.       out.println("Pirated!");     }     else {       // Give 'em the goods       out.println("Valid");       // etc...     }   }   // This method contains the algorithm used to match a key with   // a server host and port. This example implementation is extremely   // weak and should not be used by commercial sites.   //   private boolean keyFitsServer(String key, String host, int port) {     if (key == null) return false;     long numericKey = 0;     try {       numericKey = Long.parseLong(key);     }     catch (NumberFormatException e) {       return false;     }     // The key must be a 64-bit number equal to the logical not (~)     // of the 32-bit IP address concatenated with the 32-bit port number.     byte hostIP[];     try {       hostIP = InetAddress.getByName(host).getAddress();     }     catch (UnknownHostException e) {       return false;     }     // Get the 32-bit IP address     long servercode = 0;     for (int i = 0; i < 4; i++) {       servercode <<= 8;       servercode |= hostIP[i];     }     // Concatentate the 32-bit port number     servercode <<= 32;     servercode |= port;     // Logical not     long accesscode = ~numericKey;     // The moment of truth: Does the key match?     return (servercode == accesscode);   } }

This servlet refuses to perform unless given the correct key. To really make it secure, however, the simple keyFitsServer( ) logic should be replaced with a strong algorithm and the whole servlet should be run through an obfuscator to prevent decompiling. Example 4-13 later in this chapter provides the code used to generate keys. If you try this servlet yourself, it's best if you access the server with its actual name, rather than localhost , so the servlet can determine the web server's true name and IP address.

4.2.4 Getting a Context Init Parameter

Servlet init parameters, as discussed earlier, are passed to individual servlets. When multiple servlets should receive the same init parameter values, those values may be assigned as a context init parameter. The ServletContext class has two methods getInitParameter( ) and getInitParameterNames( ) for retrieving contextwide initialization information:

public String ServletContext.getInitParameter(String name) public Enumeration ServletContext.getInitParameterNames()

These methods are modeled after their counterparts in ServletConfig. getInitParameter(String name) returns the string value of the specified parameter. getInitParameterNames( ) returns an Enumeration containing the names of all the init parameters available to the web application, or an empty Enumeration if there were none.

The init parameters for a context are specified in the web.xml deployment descriptor for the context using the <context-param> tag as shown in Example 4-6.

Example 4-6. Setting Context Init Parameters in the Deployment Descriptor
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app     PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"     "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd"> <web-app>     <context-param>         <param-name>             rmihost         </param-name>         <param-value>             localhost         </param-value>     </context-param>     <context-param>         <param-name>             rmiport         </param-name>         <param-value>             1099         </param-value>     </context-param> </web-app>

Any servlet within this web application can read the context init parameters to locate the shared RMI registry, as shown in Example 4-7.

Example 4-7. Finding the Registry Using Context Init Parameters
import java.io.*; import java.rmi.registry.*; import javax.servlet.*; import javax.servlet.http.*; public class RmiDemo extends HttpServlet {   public void doGet(HttpServletRequest req, HttpServletResponse res)                     throws ServletException, IOException {     res.setContentType("text/plain");     PrintWriter out = res.getWriter();     try {       ServletContext context = getServletContext();       String rmihost = context.getInitParameter("rmihost");       int rmiport = Integer.parseInt(context.getInitParameter("rmiport"));       Registry registry = LocateRegistry.getRegistry(rmihost, rmiport);       // ...     }     catch (Exception e) {       // ...     }   } }

There's no standard mechanism to create global init parameters visible across all contexts.

4.2.5 Determining the Servlet Version

A servlet can also ask the server what Servlet API version the server supports. Besides being useful for debugging, a servlet can use this information to decide whether to use a new approach to solve a task or an older, perhaps less efficient, approach. Servlet API 2.1 introduced two methods to return the version information:

public int ServletContext.getMajorVersion() public int ServletContext.getMinorVersion()

For API 2.1, getMajorVersion( ) returns 2 and getMinorVersion( ) returns 1. Of course, these methods work only for servlets executing in servers supporting Servlet API 2.1 and later. To determine the current Servlet API version across all servers, you can use com.oreilly.servlet.VersionDetector. This class doesn't ask the server for the version; instead, it looks at the classes and variables available in the runtime and based on knowledge of the Servlet API history can calculate the current version, from 1.0 to 2.3. Because the class doesn't call getMajorVersion( ) or getMinorVersion( ), it not only works across all versions of the API, but also compiles across all versions. The VersionDetector class also can determine the current JDK version, from 1.0 to 1.3, using the same technique. This turns out to be more reliable across JVM vendor implementations than querying the System class. Example 4-8 shows the VersionDetector class. Updates to the class to support future Servlet API and JDK versions will be posted to http://www.servlets.com.

Example 4-8. The VersionDetector Class
package com.oreilly.servlet; public class VersionDetector {   static String servletVersion;   static String javaVersion;   public static String getServletVersion() {     if (servletVersion != null) {       return servletVersion;     }     // javax.servlet.http.HttpSession was introduced in Servlet API 2.0     // javax.servlet.RequestDispatcher was introduced in Servlet API 2.1     // javax.servlet.http.HttpServletResponse.SC_EXPECTATION_FAILED was     //   introduced in Servlet API 2.2     // javax.servlet.Filter is slated to be introduced in Servlet API 2.3     String ver = null;     try {       ver = "1.0";       Class.forName("javax.servlet.http.HttpSession");       ver = "2.0";       Class.forName("javax.servlet.RequestDispatcher");       ver = "2.1";        Class.forName("javax.servlet.http.HttpServletResponse")                    .getDeclaredField("SC_EXPECTATION_FAILED");       ver = "2.2";       Class.forName("javax.servlet.Filter");       ver = "2.3";      }     catch (Throwable t) {     }          servletVersion = ver;     return servletVersion;   }   public static String getJavaVersion() {     if (javaVersion != null) {       return javaVersion;     }     // java.lang.Void was introduced in JDK 1.1     // java.lang.ThreadLocal was introduced in JDK 1.2     // java.lang.StrictMath was introduced in JDK 1.3     String ver = null;     try {       ver = "1.0";       Class.forName("java.lang.Void");       ver = "1.1";       Class.forName("java.lang.ThreadLocal");       ver = "1.2";        Class.forName("java.lang.StrictMath");       ver = "1.3";      }     catch (Throwable t) {     }     javaVersion = ver;     return javaVersion;   } }

The class works by attempting to load classes and access variables until a NoClassDefFoundError or NoSuchFieldException halts the search. At that point, the current version is known. Example 4-9 demonstrates a servlet that snoops the servlet and Java version.

Example 4-9. Snooping Versions
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import com.oreilly.servlet.VersionDetector; public class VersionSnoop extends HttpServlet {   public void doGet(HttpServletRequest req, HttpServletResponse res)                         throws ServletException, IOException {     res.setContentType("text/plain");     PrintWriter out = res.getWriter();     out.println("Servlet Version: " + VersionDetector.getServletVersion());     out.println("Java Version: " + VersionDetector.getJavaVersion());   } } 


Last updated on 3/20/2003
Java Servlet Programming, 2nd Edition, © 2001 O'Reilly

< BACKCONTINUE >


Java servlet programming
Java Servlet Programming (Java Series)
ISBN: 0596000405
EAN: 2147483647
Year: 2000
Pages: 223

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net