A Web server wouldn’t be a Web server if it didn’t allow you to intercept user requests, examine them, and log them. As mentioned in Figure 7-1 shows a set of valves in a Tomcat installation.
Figure 7-1: Valves intercept requests for certain resources, and you can use them in conjunction with each other.
As Figure 7-1 shows, a valve configured at the engine level will intercept all requests to contexts on this engine, no matter what host or context they’re bound for. The valve at the host level intercepts all requests to this virtual host, and the valves in the contexts intercept only requests that are specifically for them. This means valves can be chained together to work in conjunction with each other.
Valves offer value-added functionality that includes the following:
Access logging
Single sign-on for all Web applications running on the server
Requests filtering/blocking by IP address and hostname
Detailed request dumps for debugging
Valves are nested components in the Tomcat configuration hierarchy that can be placed inside <Engine>, <Host>, or <Context> containers. (Refer to Table 7-1 describes these standard valves.
Valve Name | Description |
---|---|
Access log valve | Logs requests. |
Single sign-on valve | Lets you pass user login information to other Web applications on the server. This means the users need to log in only once, no matter how many Web applications they visit. |
Request filter valves | Enables selective filtering of incoming requests based on IP addresses or hostnames. |
Request dumper valve | Prints the headers and cookies of incoming requests and outgoing responses to a log. |
Logging access to resources is a common activity for Web server administrators. As such, Tomcat comes with a valve for logging access to resources, be that at the engine level, the host level, or the context level. The location of a log valve is fairly important from a performance point of view, because each log entry requires Tomcat to write data to disk. If you have a logging valve at the engine level of a busy server, as well as logging valves for every context, the log will be written to many times and will grow very large. However, you may need to do this to monitor each individual context as well as the whole server. This just illustrates the need for careful planning when using valves.
An access log valve isn’t a logger because a logger prints information and errors to a log file so that you can diagnose errors in Web applications. For example, if a component encounters problems and a user reports the error message to you, you’d look in the logger’s log because this is where the error will be reported. However, if you wanted to see how often a client at a certain IP address requests a certain resource, you’d examine the access log valve’s log file.
The logger’s format also depends on the application in question. If it has been written properly, all errors will be written to the error log file in a standard way so that they can be investigated and rectified. If not, you may see nasty Java stacktraces with details of the error buried among it.
The typical format for an access log valve is the common log file format, which you can find at http://www.w3.org/Daemon/User/Config/Logging.html#common-logfile-format. You may already have an analysis tool that can analyze log files in this format. If not, don’t worry— they’re quite common. AWStats (http://awstats.sourceforge.net) is a great open-source option, though you’ll need Perl to use it. Another option is Webalizer (http://www.mrunix.net/webalizer/).
Table 7-2 shows the attributes for the standard access log valve that’s supplied with Tomcat. In this case, the className attribute must be org.apache.catalina.valves.AccessLogValve.
Attribute | Description | Required? |
---|---|---|
className | The Java class of the valve. This must be org.apache.catalina.valves.AccessLogValve. | Yes |
condition | Turns conditional logging on. If set, the access log valve logs requests only if ServletRequest.getAttribute() is null. For example, if this value is set to userId, then a particular request will be logged only if ServletRequest.getAttribute("userId") == null. | No |
directory | The directory where the log files will be placed. Usually relative to the CATALINA_HOME, but you can also specify an absolute path instead. The default is logs. | No |
fileDateFormat | Allows a customized date format in the access log filename. The date format also decides how often the file is rotated. If you want to rotate every hour, then set this value to yyyy-MM-dd.HH. | No |
pattern | Specifies the format used in the log. You can customize the format, or you can use common or combined as the format (the common format plus the referrer and user-agent are logged). To customize the format, you can use any of the following patterns interspersed with a literal string: %a: Inserts remote IP address. %A: Inserts local IP address (of URL resource). %b: Inserts bytes sent count, excluding HTTP headers, shows - if zero. %B: Inserts bytes sent count, excluding HTTP headers. %D: Time taken to process the request, in milliseconds. %h: Inserts remote hostname (or IP address if the resolveHosts attribute is set to false). %H: Inserts the request protocol (HTTP). %l: Inserts remote logical user name (always '-'). %m: Inserts request method such as GET and POST. %p: Inserts the local TCP port where this request is received. %q: Inserts the query string of this request. %r: Inserts the first line of the request. %s: Inserts the HTTP status code of the response. %S: Inserts the user session ID. %t: Inserts the date and time in common log file format. %T: Time taken to process the request, in seconds. %u: Inserts the remote user that has been authenticated (otherwise it’s -). %U: Inserts the URL path of the request. %v: Inserts the name of the local virtual host from the request. %{xxx}i: Use this for incoming headers, where xxx is the header. %{xxx}c: Use this for a specific cookie, where xxx is the name of the cookie. %{xxx}r: Use this for ServletRequest attributes, where xxx is the attribute. %{xxx}s: Use this for HttpSession attributes, where xxx is the attribute. The default is common, which is %h %l %u %t "%r" %s %b. | No |
prefix | The prefix added to the name of the log file. | No |
resolveHosts | Determines if the log will contain hostnames via a reverse DNS lookup. This can take significant time if enabled. The default is false. | No |
rotatable | Determines if log rotation should occur. If false, then this file is never rotated and the fileDateFormat attribute is ignored. Use this attribute with caution because the log file could grow very large indeed. The default is true. | No |
suffix | The extension added to the name of the log file. | No |
This section contains an example access log valve to demonstrate the attributes listed in Table 7-2. By default, the access log valves in server.xml are commented out to disable them. This does, however, make it easy to activate them. Open server.xml, and navigate to the localhost <Host> entry. The access log valve is configured after the large comment section (see Listing 7-1).
Listing 7-1: The Access Log Valve in server.xml
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log." suffix=".txt" pattern="common" resolveHosts="false"/>
You may not have this entry if you’ve previously used the admin Web interface. As noted in Chapter 6, the admin application rewrites a new server.xml once you’ve made any configuration changes. This means that comments are lost, even if they contain useful default components. The good news is that the old server.xml should have been saved under another name in the conf directory, so you can copy and paste the valve entry from there into the new server.xml.
Uncomment this entry, start/restart Tomcat, and point your browser to http://localhost:8080. You should see the default Tomcat welcome page. Now examine the CATALINA_HOME/logs directory, and open the localhost_access_log.DATE.txt file. You’ll see the access log entry for the Web page itself, though you’ll also see the entries for the associated image files, all in the common log file format, as shown in Listing 7-2.
Listing 7-2: The localhost_access_log.DATE.txt Log File
127.0.0.1 - - [01/Aug/2004:20:47:54 +0000] "GET / HTTP/1.1" 200 9312 127.0.0.1 - - [01/Aug/2004:20:47:54 +0000] "GET /tomcat.gif HTTP/1.1" 200 1934 127.0.0.1 - - [01/Aug/2004:20:47:54 +0000] "GET /jakarta-banner.gif HTTP/1.1" 200 8584 127.0.0.1 - - [01/Aug/2004:20:47:54 +0000] "GET /tomcat-power.gif HTTP/1.1" 200 2324
You may want to experiment further with other attributes of the standard access log valve by modifying the previous <Valve> entry. You should experiment with other access log valve configurations, such as valves at the context level, which you configure in the appropriate context XML file, and valves at the engine level. This way you can use tools to analyze access at various levels of the server.
As a server administrator you’ll often find it useful to restrict access to certain resources. You’ve already seen password protection for administration resources, but Tomcat also allows you to use request filter valves to block access so that a user doesn’t even get as far as the password prompt. You can use this facility to block access to admin resources except for users who are on the local machine or an admin-only machine. Other options can include blocking denial-of-service (DoS) attacks or denying access to sales data for nonsales personnel, and so on.
Two types of request filter valves exist: the remote address valve and the remote host valve. The first of these filters requests by the client’s IP address, and the second filters by the client’s host. Table 7-3 shows the attributes of the remote address valve.
Attribute | Description | Required? |
---|---|---|
allow | A comma-separated list of regular expressions used to match the client’s IP address. If there’s a match, then the request is allowed through to its destination. If not, it’s blocked. If this attribute isn’t specified, then all requests are allowed except if they match a pattern in the deny attribute. | No |
className | The Java class of the valve. This must be org.apache.catalina.valves.RemoteAddrValve. | Yes |
deny | A comma-separated list of regular expressions used to match the client’s IP address. If there’s a match, then the request is blocked. If not, it’s allowed. | No |
It’s now possible to see how you can allow access only to those users on a local or admin machine. In this example, you’d add the IP address of the local (or admin) machine to the allow list. Listing 7-3 shows the scenario where both conditions are allowed (assuming the admin machine has 192.168.0.73 as its IP address).
Listing 7-3: An Example Remote Address Request Filter Valve
<Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127.0.0.1,192.168.0.73"/>
Visit a page on the local Tomcat server. You should see the page as usual. Now remove the 127.0.0.1 portion, and restart Tomcat. Visit the same page, and you should be blocked, as shown in Figure 7-2.
Figure 7-2: A blocked URL using the remote address request filter valve
Note | If a blank page isn’t a satisfactory way to indicate a blocked page, you should use servlet filters. These are part of a Web application and can be used to show custom error pages. If you’re interested in using a filter for this task, one is provided on the Apress Web site that replicates the behavior described in this section. |
You can also achieve this effect by denying access to the localhost:
<Valve className="org.apache.catalina.valves.RemoteAddrValve" deny="127.0.0.1"/>
Filtering by client host is just as easy. The only differences between the two are the class that implements the valve and the values of the regular expressions. In the case of the remote host request filter valve, the class is org.apache.catalina.valves.RemoteHostValve, and the regular expressions are hostnames instead of IP addresses.
<Valve className="org.apache.catalina.valves.RemoteHostValve" allow="*.com"/>
Note | The remote host request filter requires a reverse DNS lookup, so the server must have access to DNS. |
The request dumper valve allows you to debug Web applications by dumping the headers and cookies of requests and responses to a log. The request dumper valve uses whichever logging mechanism you’ve configured for the component that contains the valve, be that a <Logger> in Tomcat 5.0.x or Log4J in Tomcat 5.5 (see Chapter 4 for more information on logging).
You can use it for the following:
Checking how the scope of a valve affects the requests that are processed
Debugging other valves and any other request-processing components that are configured on the server
To configure a request dumper valve, modify server.xml and add the following to the context, virtual host, or engine that you want to examine:
<Valve className="org.apache.catalina.valves.RequestDumperValve"/>
If you add the request dumper valve to the default server.xml at the engine level, it will use the logger shown in Listing 7-4.
Listing 7-4: An Engine-Level Logger
<!-- Global logger unless overridden at lower levels --> <Logger className="org.apache.catalina.logger.FileLogger" prefix="catalina_log." suffix=".txt" timestamp="true"/>
For Tomcat 5.0.x, if you configure it at the host level (for the localhost host) or the context level for the default Tomcat distribution, it will use the logger shown in Listing 7-5, unless you configure a new logger for the context.
Listing 7-5: A Host-Level Logger
<Logger className="org.apache.catalina.logger.FileLogger" directory="logs" prefix="localhost_log." suffix=".txt" timestamp="true"/>
For Tomcat 5.5 you’d use the following logger for logging at the host level. Each line of the log has INFO priority.
log4j.logger.org.apache.catalina.core.ContainerBase.[Catalina].[localhost]
If you wanted logging at the context level, assuming that your context is called tomcatBook, you’d use the following:
log4j.logger.org.apache.catalina.core.ContainerBase. [Catalina].[localhost].[/tomcatBook]
This means the request dumper valve inherits the logger from a higher-level component, which isn’t always desirable if you’re troubleshooting a specific Web application’s request/ response-processing pipeline.
Once you’ve set up the valve, visit a Web application that will be covered by the valve. Once your request has been processed, open the appropriate log file. You should see something similar to Listing 7-6. The version of Tomcat and the settings of your logger will affect this, though the messages will be the same.
Listing 7-6: The Output of the Request Dumper Valve
REQUEST URI=/tomcatBook/ authType=null characterEncoding=null contentLength=-1 contentType=null contextPath=/tomcatBook cookie=JSESSIONID=7F31F129712D208903FC6F50FD5143EA header=host=localhost:8080 header=user-agent=Mozilla/5.0 (Windows; U; Windows NT 5.0; rv:1.7.3) Gecko/20040913 Firefox/0.10.1 header=accept=text/xml,application/xml,application/xhtml+xml, text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 header=accept-language=en-us,en;q=0.5 header=accept-encoding=gzip,deflate header=accept-charset=ISO-8859-1,utf-8;q=0.7,*;q=0.7 header=keep-alive=300 header=connection=keep-alive header=cookie=JSESSIONID=7F31F129712D208903FC6F50FD5143EA locale=en_US method=GET pathInfo=null protocol=HTTP/1.1 queryString=null remoteAddr=127.0.0.1 remoteHost=127.0.0.1 remoteUser=null requestedSessionId=7F31F129712D208903FC6F50FD5143EA scheme=http serverName=localhost serverPort=8080 servletPath=/index.jsp isSecure=false --------------------------------------------------------------- --------------------------------------------------------------- authType=null contentLength=-1 contentType=text/html;charset=ISO-8859-1 message=null remoteUser=null status=200 ===============================================================
As you can see, this contains a fair amount of information, all of which can be used to analyze a client’s interaction with your server. A word of warning, though: this valve decodes any parameters sent with the request using the platform’s default encoding. This may affect Web applications on the server because calls to request.setCharacterEncoding() will have no effect.
Another standard valve that’s frequently used is the single sign-on valve. Conventionally, whenever users of a Web application reaches a protected page, they will be required to log in, a process that’s repeated if they browse to another Web application on the same server. Using single sign-on, it’s possible to eliminate this repetition, provided that all the Web applications on a host use the same Tomcat realm.
The single sign-on valve caches the user’s credentials on the server and will authenticate users as they move between Web applications on a host. The credentials are cached in the client’s session, which means that a single sign-on will be effective throughout a session. The user’s browser will send a cookie with a value that uniquely identifies this user as a user who has signed in. The valve then associates the new request with the existing user credentials and allows the user to visit protected resources. This is one of the main reasons for having a common realm for the host.
Table 7-4 describes the attributes of the single sign-on valve.
Attribute | Description | Required? |
---|---|---|
className | The Java class of the valve. This must be org.apache.catalina.authenticator.SingleSignOn. | Yes |
debug | Tomcat 5.0.x only. Level of debugging. The default is zero (no debugging). | No |
requireReauthentication | Determines whether the valve should use the authentication realm to authenticate the user every time authentication is required. If false, the valve uses the cookie sent by the client and automatically authenticates the user without rechecking the realm. The default is false. | No |
Before seeing what single sign-on does, you should first experience the problem that sometimes makes it necessary to configure single sign-on. You’ll need two separate Web applications that are both protected. Luckily Tomcat comes with two such Web applications: the manager application and the admin application. If you’re using Tomcat 5.5 you’ll have to download the admin application or protect another application for this example to work.
You should already have a user who has the manager role required for access to the manager application and a user who has the admin role required for access to the admin application. You may even have a user who has both. If not, then you must create one in tomcat-users.xml now, as shown in Listing 7-7.
Listing 7-7: A User with Manager and Admin Roles Defined in tomcat-users.xml
<role rolename="manager"/> <role rolename="admin"/> <user username="tomcat" password="tomcat" roles="tomcat,manager,admin"/>
Here the tomcat user has three roles: tomcat, manager, and admin. Now start/restart Tomcat, and navigate to http://localhost:8080/manager/html/. You’ll be asked for your user details as usual. Now sign in as the user with both roles. Once you’ve done so, you should see the Web interface of the manager application.
The next step is to navigate to http://localhost:8080/admin/. You’ll be presented with the form for logging into the admin application, which means your login for the manager application hasn’t carried over into the admin application despite the details being valid for both. This is where single sign-on comes in.
Open server.xml, and navigate to the valve as shown in Listing 7-8. It’s the first valve in the localhost host, after the large commented-out section.
Listing 7-8: The Single Sign-on Valve in server.xml
<!-- Normally, users must authenticate themselves to each Web app individually. Uncomment the following entry if you would like a user to be authenticated the first time they encounter a resource protected by a security constraint, and then have that user identity maintained across *all* Web applications contained in this virtual host. --> <!-- <Valve className="org.apache.catalina.authenticator.SingleSignOn" debug="0"/> -->
Uncomment the valve, and restart Tomcat. Make sure you’ve closed your browser windows to start a new session, and navigate to http://localhost:8080/manager/html/ as before. Log in as the user with both roles as before. Once you’ve logged in successfully, navigate to http://localhost:8080/admin/. This time you won’t be asked to log in again because the valve will have recognized you from the cookie sent by your browser and will have authenticated you.