Jan Luehe's Blog

Wednesday Oct 01, 2008

Retain session data during redeployment

Retain session data during redeployment

Retain session data during redeployment

Just in time for the upcoming GlassFish v3 Prelude release, Jerome Dochez and I have added a feature that is sure to become very popular with developers: the ability to retain HTTP session data across redeploys.

Normally, when an application is redeployed, all its HTTP sessions will be destroyed, and will need to be re-established after the redeployment has completed, which may be a time consuming effort.

GlassFish v3 Prelude introduces a new keepSessions property to the asadmin redeploy command: If set to true, any active sessions of the application that is being redeployed will be serialized and saved in memory, and restored once the redeployment has completed.

Example usage:

  asadmin redeploy --properties keepSessions=true --name myapp myapp.war

If any of the active sessions of the application fail to be serialized or deserialized, a warning will be logged, and the redeployment will continue, but none of the sessions that were active prior to the redeployment will be available following the redeployment.

The new classloader of the redeployed application will be used to deserialize any sessions previously saved. The usual restrictions about serialization and deserilization apply. For example, any application specific class referenced by a session attribute may evolve only in a backwards compatible fashion.

This new GlassFish enhancement of the redeployment command will be leveraged by Netbeans' Deploy on change feature. Netbeans is going to allow developers to enable this feature (see the Preserve Sessions Across Redeployment checkbox in the NetBeans 6.5 nightly and Eclipse support for V3 Prelude snapshots), and thus increase their productivity, during their development cycles.

Friday Sep 05, 2008

GlassFish v3 adds support for Tomcat-style valves

GlassFish v3 adds support for Tomcat-style valves

GlassFish v3 adds support for Tomcat-style valves

GlassFish v3 is removing a major stumbling block that has made migration from Tomcat to GlassFish difficult, by adding "out-of-the-box" support for Tomcat-style custom valves.

Historical background on Valve incompatibility

Tomcat's org.apache.catalina.Valve interface defines a single invocation point, with this signature:

  public void invoke(Request request, Response response)
      throws IOException, ServletException;

A Tomcat valve would implement this invoke method by performing its preprocessing logic on the request/response objects, invoking the next valve in the pipeline, and performing its postprocessing logic after the invocation of the next valve in the pipeline had returned:

  public void invoke(Request request, Response response) {

      // Pre-processing logic goes here

      // Invoke next valve in the pipeline
      getNext().invoke(request, response);

      // Post-processing logic goes here

As a result of this invocation style, every valve invocation would add one frame to the call stack.

In an attempt to flatten the call stack for improved performance, Sun Application Server 8.0 changed the org.apache.catalina.Valve interface by defining two invocation points: an invoke method for the valve's preprocessing logic, and a postInvoke method for the valve's postprocessing logic. Instead of having a valve be responsible for invoking the next valve in the pipeline, a valve would return control back to the web container after completion of its invoke and postInvoke methods, respectively. In the case of invoke, the valve would also indicate to the web container whether the next valve in the pipeline should be invoked (INVOKE_NEXT), or whether the processing of the request should be aborted (END_PIPELINE), and an error response generated immediately. For example, an authentication valve would return END_PIPELINE if it failed to authenticate the client.

In summary, the Sun-modified Valve interface would be implemented as follows:

  public int invoke(Request request, Response response) {
      // Pre-processing logic goes here
      return INVOKE_NEXT;
          OR
      return END_PIPELINE;
  }

  public void postInvoke(Request request, Response response) {
      // Post-processing logic goes here
  }

Valve migration issues from Tomcat to GlassFish

GlassFish v1 inherited the modified Valve interface from Sun Application Server 8.0. At the time, there was no compatibility issue with Tomcat, though, because valves were an implementation detail of the web container.

The incompatible changes to the Valve interface started to become an issue only when GlassFish v2 added support for custom valves. Custom valves could be configured at the virtual server and web application levels, and would be specified in form of a <virtual-server> property in domain.xml, or a web application property in sun-web.xml, respectively, by using the fully-qualified class name of the valve as the property value.

Once GlassFish v2 started supporting custom valves, Tomcat users who were trying to migrate their custom valves from Tomcat to GlassFish hit a roadblock, because their valves simply would not work in GlassFish, unless they were adapted to the Sun-modified Valve interface and recompiled (which, understandably, was not an option for most developers).

Valve alignment benefits in GlassFish v3

In an attempt to smoothen the migration from Tomcat to GlassFish, GlassFish v3 supports Tomcat-style valves out-of-the-box. This has been accomplished by a number of changes. First, the Sun-modified version of the Valve interface has been renamed and moved to a separate package. It now lives in org.glassfish.web.valve.GlassFishValve, and has "made room" for the org.apache.catalina.Valve interface from Tomcat in its original form.

By default, GlassFish v3 retains the performance benefits gained from the flattened valve invocation. Developers who would like to take advantage of the more efficient invocation style for their custom valves may choose to implement the org.glassfish.web.valve.GlassFishValve interface.

On the other hand, developers who would like to migrate their valves from Tomcat to GlassFish v3 can now do so, without having to change and recompile them: Their valves will work out-of-the-box!

Not only does GlassFish v3 support Tomcat-style valves, but it also preserves binary compatibility with valves compiled against the Sun-modified org.apache.catalina.Valve interface.

As an added benefit, GlassFish- and Tomcat-style valves may be mixed-and-matched, and added to a pipeline in any order!

Valve alignment implementation details

Following are some implementation details on how this all works:

All valves that are part of the web container implementation are implemented as hybrid valves, meaning they support both the Tomcat- and GlassFish invocation styles. By default, they are used in GlassFish mode, for improved performance.

Once a Tomcat-style valve has been added to a pipeline, any valves subsequently added to the same pipeline must also be of Tomcat style (this is because a Tomcat-style valve does not return control to the web container): If they are not already of Tomcat style, they will be wrapped inside a TomcatValveAdapter. Likewise, any valve that implements the modified Sun-modified org.apache.catalina.Valve interface is turned into an instance of org.glassfish.web.valve.GlassFishValve, by wrapping it with a GlassFishValveAdapter.

Please try out your Tomcat valves with GlassFish v3 and report back!

Friday Apr 04, 2008

Converged Http Sessions in SailFin

Converged Http Sessions in SailFin

Converged Http Sessions in SailFin

Introduction

ConvergedHttpSession is a new feature introduced by JSR 289, which provides access to SIP protocol sessions from HTTP and vice versa. This enables the creation of unified, cross-protocol communication services, where SIP calls may be initiated and terminated from a web browser.

A popular example of such a use case is the Click-to-dial application (see below), which enables a client to initiate a SIP call by simply clicking on a URL link.

A ConvergedHttpSession implements the javax.servlet.sip.ConvergedHttpSession interface, which in turn extends the javax.servlet.http.HttpSession interface defined by the Servlet specification.

This blog explains the motivation behind a converged application, provides a step-by-step analysis of how a ConvergedHttpSession may be used to initiate and terminate a SIP call from the HTTP servlet of a converged application, explains how the routing decisions of SailFin's Converged Loadbalancer influence the session id of a SipApplicationSession created from a ConvergedHttpSession, and finally shows how a ConvergedHttpSession fits into SailFin's in-memory replication framework that provides high-availability of HTTP and SIP artifacts in a SailFin cluster.

Converged Applications

A converged application combines SIP protocol functionality with other protocols (generally HTTP) to provide a unified communication service. It bundles both sip.xml and web.xml deployment descriptors for its SIP and HTTP servlet components, respectively.

In a JSR 289 compliant container, such as SailFin, HTTP sessions are exposed to HTTP servlets either as instances of javax.servlet.http.HttpSession or javax.servlet.sip.ConvergedHttpSession, depending on the type of application: In the case of a pure web application (i.e., one that has a web.xml, but no sip.xml), HTTP sessions are exposed as instances of javax.servlet.http.HttpSession, whereas in the case of a converged application, they are exposed as instances of javax.servlet.sip.ConvergedHttpSession.

This means that developers of HTTP servlets that are part of a converged application may cast the result of a call to HttpServletRequest.getSession() to javax.servlet.sip.ConvergedHttpSession, as shown in the following code snippet:

  import java.io.IOException;
  import javax.servlet.ServletException;
  import javax.servlet.http.HttpServlet;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import javax.servlet.sip.ConvergedHttpSession;

  public class ConvergedServlet extends HttpServlet {

      public void doGet(HttpServletRequest httpReq, HttpServletResponse httpRes)
              throws ServletException, IOException {

          ConvergedHttpSession chs = (ConvergedHttpSession) httpReq.getSession();
          [...]
      }

      [...]
  }

If invoked from the HTTP servlet of a pure web application, the above cast to ConvergedHttpSession would result in a ClassCastException.

Through its ConvergedHttpSession interface, an HttpSession may be associated with (that is, added as a child protocol session to) a javax.servlet.sip.SipApplicationSession. Once this type of association has occurred, the ConvergedHttpSession interface allows navigation from the HttpSession to its SipApplicationSession parent and SipSession siblings, as shown in the following code snippet:

  import javax.servlet.sip.ConvergedHttpSession;
  import javax.servlet.sip.SipApplicationSession;
  import javax.servlet.sip.SipSession;

  ConvergedHttpSession chs = (ConvergedHttpSession) httpReq.getSession();
  SipApplicationSession sas = chs.getApplicationSession();
  Iterator sipSessions = sas.getSessions("SIP");

Click-To-Dial Use Case

The following example assumes a converged application with one HTTP servlet (ClickToDialServlet) and one SIP servlet (CallServlet). The example focuses on the HTTP servlet, because it is more relevant in terms of the use of ConvergedHttpSession.

The ClickToDialServlet allows to initiate and terminate a SIP call from sip:foo@<localIp>:<localSipPort> to sip:bar@<remoteIp>:<remoteSipPort>, where remoteIp and remoteSipPort are passed in as HTTP request parameters. It either initiates or terminates a SIP call, based on the value of the action request parameter:

  @Resource(mappedName = "sip/convergedapp")
  private SipFactory sipFactory;

  public class ClickToDialServlet extends HttpServlet {

      public void doGet(HttpServletRequest httpReq, HttpServletResponse httpRes)
            throws ServletException, IOException {

          // Initiate or terminate a SIP call, depending on the value of the
          // "action" request parameter
          String action = httpReq.getParameter("action");
          if (action.equals("call")) {
              initiateSipCall(httpReq, httpRes);
          } else if (action.equals("bye")) {
              terminateSipCall(httpReq, httpRes);
          } else {
              throw new ServletException("Invalid action");
          }
      }

      [...]
  }

The initiateSipCall and terminateSipCall methods of the ClickToDialServlet are implemented as follows:

  /**
   * Initiates a SIP call
   */
  private void initiateSipCall(HttpServletRequest httpReq, HttpServletResponse httpRes) throws ServletException, IOException {

      // Create a new ConvergedHttpSession
      ConvergedHttpSession chs = (ConvergedHttpSession) httpReq.getSession();

      // Create a new SipApplicationSession, and link the ConvergedHttpSession to it
      SipApplicationSession sas = chs.getApplicationSession();

      // Create the SIP URIs that will be included in the To and From fields of the SIP INVITE request.
      // The SipFactory to create the SIP URIs has been injected into this servlet, using the @Resource annotation (see above)
      String localIp = InetAddress.getLocalHost().getAddress().toString();
      Address fromAddr = sipFactory.createAddress("sip:foo@" + localIp + ":" + System.getProperty("SIP_PORT"));
      Address toAddr = sipFactory.createAddress("sip:bar@" + httpReq.getParameter("remoteIp") + ":" + httpReq.getParameter("remotePort"));

      // Create the SIP INVITE request using the SipFactory and the toAddr and fromAddr addresses
      SipServletRequest sipReq = sipFactory.createRequest(sas, "INVITE", fromAddr, toAddr);

      // Create a SipSession
      SipSession sipSession = sipReq.getSession();

      // Have any requests and responses related to the new SipSession be handled by the converged application's CallServlet
      sipSession.setHandler("CallServlet");

      // Store the id of the SipSession as a session attribute with name "CallId" in the parent SipApplicationSession,
      // so that this SipSession may later be found and used to terminate the call
      sas.setAttribute("CallId", sipSession.getId());

      // Send the request
      sipReq.send();

      // Return a web form that may be used to terminate the call
      PrintWriter out = httpRes.getWriter();
      out.println("<form action=\"ClickToDialServlet\" method=\"GET\">");
      out.println("<input type=\"hidden\" name=\"action\" value=\"bye\"/>");
      out.println("<input type=\"submit\" value=\"Terminate call\"/>");
      out.println("</form>");
  }

  /**
   * Terminates a SIP call.
   *
   * This method is invoked when the FORM returned by the initiateSipCall
   * method of this servlet is submitted.
   */
  private void terminateSipCall(HttpServletRequest httpReq, HttpServletResponse httpRes) throws ServletException, IOException {

      // Resume the ConvergedHttpSession, which is identified by the JSESSIONID cookie sent with the request
      ConvergedHttpSession chs = (ConvergedHttpSession) httpReq.getSession(false);

      // Get the SipApplicationSession parent
      SipApplicationSession sas = chs.getApplicationSession();

      // Get the SipSession whose id had been stored as a session attribute with name "CallId" in the parent SipApplicationSession
      SipSession sipSession = sas.getSipSession((String) sas.getAttribute("CallId"));

      // Send a BYE to the UAS to terminate the call
      sipSession.createRequest("BYE").send();
  }

Colocation Requirements

Calling getApplicationSession() on a ConvergedHttpSession that has not yet been associated with any SipApplicationSession will create a new SipApplicationSession and make it the parent of the ConvergedHttpSession, unless the HTTP request URL that was used to create or resume the ConvergedHttpSession contains a parameter with name sipappsessionid, in which case the corresponding SipApplicationSession will be looked up and made the parent of the ConvergedHttpSession.

If ConvergedHttpSession.getApplicationSession() ends up creating a SipApplicationSession in a SailFin cluster, and the cluster is load-balanced by the Converged Loadbalancer (CLB) with its consistent-hash load-balancing algorithm enabled, special precautions are taken by SailFin's converged container as it forms the id of the new SipApplicationSession.

In SailFin, the id of a SipApplicationSession consists of three components:

  • a key component, which is used to determine the instance where (according to the consistent hash algorithm) the SipApplicationSession belongs,
  • an appName, which identifies the application to which the SipApplicationSession belongs, and
  • a random uuid.

In its consistent-hash mode, the CLB computes a hashkey from the header values of an initial request, based on the rules specified in the Data Centric Rules (DCR) configuration file. This hashkey is used to look up, with the help of a consistent hash function, the cluster instance that is supposed to service the request, and the CLB then forwards the request to this instance. The hashkey, which is computed only once per initial request, is included in the response in the form of a BEKey.

For example, in the case of a 200 OK SIP response, the BEKey is included in the Contact header, and in the case of a SIP response with a status code greater than or equal to 300, it is included in the To header. In the case of a HTTP response, the BEKey is returned as a cookie, or encoded in the URL if cookies have been disabled.

The presence of a BEKey in subsequent requests ensures that these requests will be routed by the CLB to the same instance that processed the initial request, unless that instance has gone down in the meantime, in which case the BEKey will have been remapped to a different instance.

See the CLB Functional Specification and blog for details.

For example, the submission of the FORM returned by the initiateSipCall method of the ClickToDialServlet in the above example may result in the following HTTP request:

  GET /convergedapp/ClickToDialServlet?action=bye HTTP/1.1
  Host: [...]
  Cookie: BEKey=hashkey; JSESSIONIDVERSION=/convergedapp:0; JSESSIONID=0045773156aee0763caf8862c242
  [...]

Notice how the BEKey is carried in the request's Cookie header.

When the CLB intercepts and proxies an initial or subsequent HTTP request, it adds the BEKey as a header with name proxy-bekey to the HTTP request.

If a call to ConvergedHttpSession.getApplicationSession() ends up creating a new SipApplicationSession, SailFin's converged container checks the HTTP request for the proxy-bekey header, and if present, assigns its value to the key component of the id of the newly generated SipApplicationSession.

This ensures that the instance to which a request that resumes a ConvergedHttpSession is routed by the CLB, and the instance on which the ConvergedHttpSession's associated SipApplicationSession belongs (according to the consistent hash algorithm), always match.

High-Availability

SailFin provides high-availability of SIP and HTTP session artifacts in a cluster of SailFin instances, by leveraging and extending the in-memory replication feature that was first introduced in GlassFish v2. SailFin's high-availability feature, which is also known under the acronym SSR (Sip Session Retainability), will be covered in a separate blog.

Once a ConvergedHttpSession has been associated with a SipApplicationSession, the session id of the SipApplicationSession is stored as an instance field in the ConvergedHttpSession and becomes part of that session's serialized representation. The fact that an HTTP session being replicated may really be an instance of javax.servlet.sip.ConvergedHttpSession remains hidden from the in-memory replication framework.

Should a ConvergedHttpSession become separated from its parent SipApplicationSession in a cluster after a failover, it will be possible for the ConvergedHttpSession to retrieve its parent SipApplicationSession, based on its id.

Tuesday Feb 12, 2008

Alternate Docroots and Local Resource Paths

Alternate Docroots and Local Resource Paths

Alternate Docroots and Local Resource Paths

Judging from the traffic on the various GlassFish mailing lists and user forums, it seems that support for alternate docroots in GlassFish has become very popular.

However, from the feedback given, it looks like the way alternate docroot paths are computed by the web container deserves additional coverage: Several developers have tried leveraging alternate docroots and were left wondering why the container would return a 404 response instead of the contents of the requested resource.

Hopefully, this blog will clarify things.

When configuring an alternate docroot, try keeping in mind the following rules:

  • Alternate docroots (more specifically, their "from" values) are matched against a request's path info (obtained by calling javax.servlet.http.HttpServletRequest.getPathInfo()).
  • The local path of a resource whose request has been matched by an alternate docroot is computed by appending the request's path info to the alternate docroot's "dir" value.

As an example, consider the following alternate docroot declaration in sun-web.xml:

  <property name="alternatedocroot_1" value="from=/orderstore/* dir=C:/stryker_cci/orderstore"/>

and this request URL:

  http://localhost:8080/CIWeb/orderstore/test.txt

Assume the request is mapped to a web application deployed at /CIWeb, meaning the request URL's context root component is given as /CIWeb, and its path info component is given as /orderstore/test.txt, which is matched by the above alternate docroot. The local filesystem path where the requested resource will be looked up is given as the value of the alternate docroot's "dir" value:

  C:/stryker_cci/orderstore

with the request's path info:

  /orderstore/test.txt

appended to it, resulting in:

  C:/stryker_cci/orderstore/orderstore/test.txt

As another example, consider the following alternate docroot declaration in sun-web.xml:

  <property name="alternatedocroot_1" value="from=/myimages/* dir=/images"/>

and this request URL:

  http://localhost:8080/myimages/image1.jpg

Further assume that the above request is mapped to a web application deployed at the root context ("/"). In this case, the request's path info evaluates to:

 /myimages/image1.jpg

meaning it is matched by the above alternate docroot. The local filesystem path where the requested resource will be looked up is given as the value of the alternate docroot's "dir" value:

  /images

with the request's path info:

  /myimages/image1.jpg

appended to it, resulting in:

  /images/myimages/image1.jpg

Friday Dec 14, 2007

GlassFish Support for Cookie-less HTTP Sessions

GlassFish Support for Cookie-less HTTP Sessions

GlassFish Support for Cookie-less HTTP Sessions

Recently, we have come across an interesting technical requirement posted to users@glassfish.dev.java.net by Evaristo Jose Camarero:

The technical requirement centered around the ability to create and resume HTTP sessions from mobile client devices, without the ability to leverage HTTP cookies or URL rewriting facilities. Instead, HTTP sessions would be identified by the mobile clients' IP addresses. The inherent limitation that each mobile device could handle only one session (per web application) at a time was deemed acceptable. Would it be possible to support such a resource constrained environment with GlassFish? The answer is: Yes!

In GlassFish V2, we added support for allowing web application developers to extend the web container by injecting their own custom valves into the container's request and response processing framework.

I realized that we could leverage this extension mechanism in combination with a special session management configuration property to support the above requirement: What if we developed an application-specific valve that sets the client's IP address (available via the standard javax.servlet.ServletRequest.getRemoteAddr() Servlet API method) as the requested session id on every intercepted request?

The following valve implementation code illustrates the solution:

package my.package;

import java.io.IOException;
import javax.servlet.ServletException;
import org.apache.catalina.Request;
import org.apache.catalina.Response;
import org.apache.catalina.Valve;
import org.apache.coyote.tomcat5.CoyoteRequest;

/**
 * Valve responsible for setting the client's IP address as the HTTP session id.
 */
public class SessionIdValve implements Valve {

    private static final String INFO = "Valve responsible for setting the client's IP address as the HTTP session id";

    public String getInfo() {
        return INFO;
    }

    /**
     * Invoked "on the way in".
     */
    public int invoke(Request request, Response response)
            throws IOException, ServletException {
        CoyoteRequest coyoReq = (CoyoteRequest) request;
        coyoReq.setRequestedSessionId(coyoReq.getRemoteAddr());
        return INVOKE_NEXT;
    }

    /**
     * Invoked "on the way out".
     */
    public void postInvoke(Request request, Response response)
            throws IOException, ServletException {
        // Deliberate no-op
    }
}

Since an application-specific valve is given the opportunity to intercept any request that has been routed to the web application before the request is passed to application code, the above valve can be used to assign the client's IP address to any new HTTP session generated for the client, and to resume an existing HTTP session on behalf of the client.

To make the above solution work, the following sun-web.xml deployment descriptor must be bundled in the web application's WEB-INF directory:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 8.1 Servlet 2.4//EN" "http://www.sun.com/software/sunone/appserver/dtds/sun-web-app_2_4-1.dtd">

<sun-web-app>
  <property name="valve_1" value="my.package.SessionIdValve"/>
  <property name="reuseSessionID" value="true"/>
</sun-web-app>

The property with name valve_1 causes the valve whose fully qualified name my.package.SessionIdValve is given as the property's value, and whose class file must be available in the web application's WEB-INF/classes directory, or in one of the JAR files in the web application's WEB-INF/lib directory, to be instantiated during deployment and injected into the web application's request processing path.

The other property: reuseSessionID, allows the valve to instruct the session manager (assigned by the web container to the web application) to use the client's IP address as the session id for any new sessions, rather than generating a random value.

Evaristo tried the proposed solution in his environment and reported back that he was very satisfied with it.

This solution may also be used in combination with a GlassFish cluster and its in-memory session replication feature.

Monday Dec 03, 2007

How to Loadbalance GlassFish Cluster with Apache Loadbalancer

How to Loadbalance GlassFish Cluster with Apache Loadbalancer

How to Loadbalance GlassFish Cluster with Apache Loadbalancer

Since GlassFish V1, it has been possible to front-end a GlassFish instance with Apache's httpd web server, after following a few simple configuration steps, which include defining the com.sun.enterprise.web.connector.enableJK system property on the GlassFish instance, and specifying the port number of the mod_jk listener on the GlassFish instance as its value. By specifying this system property, the mod_jk connector, which comes standard with GlassFish (minus the JAR files that need to be copied from a Tomcat installation as per the configuration steps referenced above), will be started automatically and will listen on the specified port to any traffic sent by the httpd front-end over the AJP protocol. (Please notice that when you follow the configuration steps referenced above, you must use the tomcat-ajp.jar from Tomcat 5.5.23. Using the tomcat-ajp.jar bundled with a more recent Tomcat release will not work.)

A common use case for front-ending GlassFish with httpd is to have httpd serve any requests for static resources, while having any requests for dynamic resources, such as servlets and JavaServer(TM) Pages (JSPs), forwarded to, and handled by the GlassFish backend instance.

However, up until now, support for Apache's httpd has been limited to a single GlassFish instance, and there has been great interest on the GlassFish user forum in having an entire cluster of GlassFish instances load-balanced by Apache, allowing users to transition from an Apache-loadbalanced cluster of Tomcat instances to an Apache-loadbalanced cluster of GlassFish instances and take advantage of the in-memory session replication feature introduced in GlassFish V2.

We have listened to the GlassFish user community and added the requested feature to the SJSAS 9.1 UR1 release. In other words, with the upcoming SJSAS 9.1 UR1 release, it will be possible to load-balance a cluster of GlassFish instances with Apache.

In order to support stickiness, Apache's loadbalancer relies on a jvmRoute to be included in any JSESSIONID received by it. The jvmRoute, which is separated from the session id via ".", and whose value is configured via a system property of the same name, identifies the cluster instance on which the HTTP session was generated, or on which it was last resumed. This means that every GlassFish instance in a cluster that is front-ended by Apache's loadbalancer must be configured with a jvmRoute system property whose value is unique within the cluster.

For example, if an HTTP session was generated on a cluster instance with a jvmRoute system property equal to instance1, the JSESSIONID returned to the client (via an HTTP cookie or URL rewriting) will contain the session id with the string .instance1 appended to it. A subsequent request that is intercepted by the Apache loadbalancer will include the same JSESSIONID value that was returned to the client, from whose jvmRoute suffix the Apache loadbalancer can determine the instance on which the HTTP session was last served, and direct the request to it. Should that instance have failed in the meantime, the Apache loadbalancer will select a different instance from the remaining healthy instances, and have the request failover to it. For example, if the request fails over to an instance whose jvmRoute system property is equal to instance2, the response generated from that instance will include a JSESSIONID containing the session id with .instance2 (instead of .instance1) appended to it.

The challenge we were facing when adding support for the jvmRoute feature to GlassFish has been that while the Apache loadbalancer expects the jvmRoute, whose value may change over the lifetime of its associated HTTP session, to be part of the JSESSIONID, we had to shield the session management in GlassFish from the jvmRoute, to preserve the invariant (from the session management's perspective) that session ids are immutable and remain constant over the lifetime of a session.

We've addressed this challenge by having the web container strip any jvmRoute off an incoming JSESSIONID (and use the remainder as the session id of the session to be resumed), and append a jvmRoute to the session id when forming a JSESSIONID. Of course, we have the web container process a JSESSIONID in this way only if the jvmRoute system property has been set.

One of the side effects of this change has been that since a jvmRoute is dynamic, the web container now adds a JSESSIONID cookie to every response, regardless of whether an HTTP session was created or resumed by the corresponding request, provided that the jvmRoute system property has been set.

The remainder of this blog covers important configuration aspects.

In order to load-balance a GlassFish cluster via Apache, follow these steps:

  1. Define the jvmRoute and com.sun.enterprise.web.connector.enableJK system properties at the GlassFish cluster level. For example, in the case of a cluster named "cluster1", run these commands:
      asadmin create-jvm-options --target cluster1 "-DjvmRoute=\${AJP_INSTANCE_NAME}"
      asadmin create-jvm-options --target cluster1 "-Dcom.sun.enterprise.web.connector.enableJK=\${AJP_PORT}"
    
  2. Configure the above system properties for each instance in the cluster. For example, for a cluster instance named "instance9", run these commands:
      asadmin create-system-properties --target instance9 AJP_INSTANCE_NAME=instance9
      asadmin create-system-properties --target instance9 AJP_PORT=8020
    
    Notice how the port number (8020) specified for the mod_jk connector on "instance9" matches the value of the corresponding worker.instance9.port in the sample workers.properties below.

  3. List each GlassFish instance, including the port number of its mod_jk connector, in Apache's workers.properties configuration file. Make sure that the name of each worker equals the value of the jvmRoute system property of the GlassFish instance to which the worker connects. This convention makes it possible for an HTTP session to remain sticky to the GlassFish instance on which the session was created, or on which the session was last resumed.

  4. The following sample workers.properties configuration file is used to load-balance a 9-instance GlassFish cluster, in which the instances are spread over three physical server machines: my.domain1.com, my.domain2.com, and my.domain3.com:
      # Define 1 real worker using ajp13
      worker.list=loadbalancer
      # Set properties for instance1
      worker.instance1.type=ajp13
      worker.instance1.host=my.domain1.com
      worker.instance1.port=8012
      worker.instance1.lbfactor=50
      worker.instance1.cachesize=10
      worker.instance1.cache_timeout=600
      worker.instance1.socket_keepalive=1
      worker.instance1.socket_timeout=300
      # Set properties for instance4
      worker.instance4.type=ajp13
      worker.instance4.host=my.domain1.com
      worker.instance4.port=8015
      worker.instance4.lbfactor=50
      worker.instance4.cachesize=10
      worker.instance4.cache_timeout=600
      worker.instance4.socket_keepalive=1
      worker.instance4.socket_timeout=300
      # Set properties for instance7
      worker.instance7.type=ajp13
      worker.instance7.host=my.domain1.com
      worker.instance7.port=8018
      worker.instance7.lbfactor=50
      worker.instance7.cachesize=10
      worker.instance7.cache_timeout=600
      worker.instance7.socket_keepalive=1
      worker.instance7.socket_timeout=300
      # Set properties for instance2
      worker.instance2.type=ajp13
      worker.instance2.host=my.domain2.com
      worker.instance2.port=8013
      worker.instance2.lbfactor=50
      worker.instance2.cachesize=10
      worker.instance2.cache_timeout=600
      worker.instance2.socket_keepalive=1
      worker.instance2.socket_timeout=300
      # Set properties for instance5
      worker.instance5.type=ajp13
      worker.instance5.host=my.domain2.com
      worker.instance5.port=8016
      worker.instance5.lbfactor=50
      worker.instance5.cachesize=10
      worker.instance5.cache_timeout=600
      worker.instance5.socket_keepalive=1
      worker.instance5.socket_timeout=300
      # Set properties for instance8
      worker.instance8.type=ajp13
      worker.instance8.host=my.domain2.com
      worker.instance8.port=8019
      worker.instance8.lbfactor=50
      worker.instance8.cachesize=10
      worker.instance8.cache_timeout=600
      worker.instance8.socket_keepalive=1
      worker.instance8.socket_timeout=300
      # Set properties for instance3
      worker.instance3.type=ajp13
      worker.instance3.host=my.domain3.com
      worker.instance3.port=8014
      worker.instance3.lbfactor=50
      worker.instance3.cachesize=10
      worker.instance3.cache_timeout=600
      worker.instance3.socket_keepalive=1
      worker.instance3.socket_timeout=300
      # Set properties for instance6
      worker.instance6.type=ajp13
      worker.instance6.host=my.domain3.com
      worker.instance6.port=8017
      worker.instance6.lbfactor=50
      worker.instance6.cachesize=10
      worker.instance6.cache_timeout=600
      worker.instance6.socket_keepalive=1
      worker.instance6.socket_timeout=300
      # Set properties for instance9
      worker.instance9.type=ajp13
      worker.instance9.host=my.domain3.com
      worker.instance9.port=8020
      worker.instance9.lbfactor=50
      worker.instance9.cachesize=10
      worker.instance9.cache_timeout=600
      worker.instance9.socket_keepalive=1
      worker.instance9.socket_timeout=300
    
      worker.loadbalancer.type=lb
      worker.loadbalancer.balance_workers=instance1,instance2,instance3,instance4,instance5,instance6,instance7,instance8,instance9
    
  5. Reference the loadbalancer worker specified in your workers.properties file from your httpd.conf. The following snippet from httpd.conf causes any JSP requests to be load-balanced over the GlassFish cluster configured in the above workers.properties file:
      JkWorkersFile workers.properties
      # Loadbalance all JSP requests over GlassFish cluster
      JkMount /*.jsp loadbalancer
    

As soon as the cluster instance to which an HTTP session has been sticky has failed, the loadbalancer will route any subsequent requests for the same HTTP session to a different instance. This instance will be able to load and resume the requested session using the in-memory session replication feature that has been available since GlassFish V2. The in-memory session replication feature is enabled only for those web applications that have been marked as distributable in their web.xml deployment descriptor, and that have been deployed to the cluster with the --availabilityenabled option of the asadmin deploy command set to true (default is false).

Monday Sep 17, 2007

New Web Container Features in GlassFish V2

New Web Container Features in GlassFish V2

New Web Container Features in GlassFish V2

GlassFish V2 has been released today. Following is a short summary of the new web container features available in this release (please see Jeanfrancois Arcand's blog for new Grizzly related features in GlassFish V2, including support for Comet and HTTP compression):
  • Alternate docroots:

    Alternate docroots allow a virtual server or web application to use docroots other than the docroot assigned to them by the web container. Alternate docroots are configured with, and matched by, request URL patterns, similar to how requests are mapped to servlets. Alternate docroots may be configured either at the virtual server level or for individual web applications.

    One of the many use cases made possible by alternate docroots is the sharing of resources across multiple web applications, by storing these resources in alternate docroots instead of bundling them with each web application.

  • Dynamic reconfiguration:

    All aspects of the web container, including the creation and removal of HTTP listeners and virtual servers, as well as any changes to existing HTTP listeners and virtual servers, are now dynamically reconfigurable, meaning they no longer require any server restart in order to take effect.

  • In-memory compilation of JavaServer(TM) Pages (JSPs):

    The JSP compiler has taken advantage of the Java(TM) Compiler API, which has been standarized under JSR 199. This means that the compilation of the servlet derived from a JSP page may now be performed programmatically by invoking the Java Compiler API, instead of by running "javac" on a forked process. This also means that the generated .java and .class files no longer need to be written to, or read from, disk. Overall, this feature, which is available only when using a Java 6 based runtime, has improved the first-access response time of JSP pages dramatically. See Kin-Man Chung's blog for details.

  • In-memory replication of HTTP sessions:

    GlassFish V2 supports a new session persistence type called in-memory replication, which provides high availability and failover capabilities of HTTP sessions and stateful session beans in a cluster environment, without persisting the sessions to a database (as is the case with HADB). The web container has added versioning support to HTTP sessions, to allow the replication framework to detect a failover situation, identify stale session data, and fetch, either from its local replication cache or from another instance in the cluster, the most recent version of an HTTP session that would satisfy the client request.

  • Smoother migration support from Tomcat:

    In order to allow for a smoother migration from Tomcat, GlassFish V2 now detects and processes Tomcat's context.xml configuration files. See Amy Roh's blog for details.

  • Improved I18N support:

    GlassFish V2 has improved support for web applications with multi-byte requirements. For example, multi-byte request URLs are now supported out-of-the-box, by using UTF-8 (instead of ISO-8859-1) as the default URL decoding. Alternative decodings may now be specified through a configuration property at the http-service and http-listener levels in domain.xml. In addition, multi-byte context roots and RFC 3490 style, internationalized domain names are now supported.

  • Virtual server security realms:

    A virtual server may now be configured with its own security realm, avoiding the need for individual web applications deployed on the virtual server to specify their own: They simply inherit the security realm of the virtual server on which they have been deployed, but can still override it with their own (as specified in their deployment descriptor) if needed.

  • Self-contained Expression Language:

    The JSP 2.1 Specification (JSR 245) has undergone a Maintenance Release (MR) in an effort to take the Expression Language (EL) out of JSP 2.1 and turn it into a self-contained specification.

  • Support for pluggable network authentication modules:

    Glassfish V2 is the reference implementation for a new standard, the Authentication SPI for Containers (aka, JSR 196). This standard is a general purpose framework which extends the pluggable authentication model of JAAS to the authentication of network messages. Support by the Glassfish web container for the Servlet profile of the 196 standard exposes a simple contract by which implementations of network authentication mechanisms can be configured (in the field) for use by the Glassfish web container in its processing of security contraints on behalf of portable web applications. The JSR 196 standard is available on the jcp web site.

  • Various performance optimizations:

    We've improved performance at various levels, both during web container startup as well as during normal request processing.

    For example, all web modules are now started concurrently when the web container is brought up.

    In addition, a virtual server's default web module is no longer implemented as a separate web module (it used to be implemented as an exact copy of the web module that had been declared as the virtual server's default web module). Instead, we've enhanced the container's request mapping algorithm by making it default web module aware, so that any requests mapped to the virtual server's root context are now routed to the web module that is acting as the virtual server's default web module (if any default web module has been configured for the virtual server). This has improved the container's startup performance, since it reduces the number of web modules that need to be started. In some cases, as with the admingui (which acts as the default web module of the admin virtual server), the savings have been significant, since the admingui used to initialize the JavaServer Faces (JSF) runtime during its startup, meaning the JSF runtime used to be initialized twice during server startup - and initializing the JSF runtime is not cheap!

    We've not only avoided initializing the admingui's JSF runtime twice during startup, but we actually went one step further by deferring the initialization of the admingui's JSF runtime to the time when the first request for the admingui is received.

    See Prashanth Abbagani's blog for how (in numbers) each of the above optimizations has contributed to improving startup performance.

    We also improved the way the Servlet and JSP containers cooperate, by having the Servlet container pass information about a web application's Tag Library Descriptor (TLD) related mappings to the JSP container, so that the JSP container does not need to parse this information again.

    In addition, the JSP compiler now creates a JSP tag library instance only once per web application (so that it may be shared by all the JSP pages in the web application that import it via a taglib directive or namespace declaration), instead of once per translation unit, which has improved JSP performance and reduced memory footprint.

Friday Apr 27, 2007

How a Jruby-Based Web Application Became Unusable Following its Redeployment. Lessons Learned

How a Jruby-Based Web Application Became Unusable Following its Redeployment. Lessons Learned

How a Jruby-Based Web Application Became Unusable Following its Redeployment. Lessons Learned

    Recently, Ashish Sahni, a GlassFish developer, reported a mysterious problem: He had deployed a Jruby-based web application to GlassFish, and when he tried to redeploy it, he noticed errors of the form java.lang.ThreadDeath in the server.log (in fact, the subsequent deployment of any web application would cause this error to be logged), and his web application had become unusable.

    How could the deployment and undeployment of a Jruby-based web application affect the subsequent deployment of unrelated web applications? And worse, how could it render the Jruby-based web application unusable?

    To investigate this issue, I looked at the stacktrace of the ThreadDeath error that was logged during redeployment:

    
      java.lang.ThreadDeath
        at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1325)
        ...
        at java.lang.Class.newInstance(Class.java:303)
        at java.security.Provider$Service.newInstance(Provider.java:1130)
        at sun.security.jca.GetInstance.getInstance(GetInstance.java:220)
        at sun.security.jca.GetInstance.getInstance(GetInstance.java:147)
        at java.security.Security.getImpl(Security.java:658)
        at java.security.MessageDigest.getInstance(MessageDigest.java:122)
        at java.io.ObjectStreamClass.computeDefaultSUID(ObjectStreamClass.java:1731)
        ...
        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:302)
        at com.sun.enterprise.instance.SerializedDescriptorHelper.storeSerializedDescriptor(SerializedDescriptorHelper.java:239)
        at com.sun.enterprise.instance.SerializedDescriptorHelper.store(SerializedDescriptorHelper.java:143)
        at com.sun.enterprise.instance.SerializedDescriptorHelper.store(SerializedDescriptorHelper.java:123)
        at com.sun.enterprise.instance.BaseManager.saveAppDescriptor(BaseManager.java:684)
        ...
        at com.sun.enterprise.management.deploy.DeployThread.deploy(DeployThread.java:174)
        at com.sun.enterprise.management.deploy.DeployThread.run(DeployThread.java:210)
    

    I noticed the ThreadDeath error was thrown when the deployment code was attempting to serialize the web application's descriptor files, and in doing so, requested a MessageDigest instance for computing a serialization UID.

    The serialization (and deserialization) of application descriptors has been a performance enhancement developed by the deployment team. The code catches and logs any Throwable (along with its stacktrace) that may emerge from the attempt to serialize an application descriptor, and continues. Although the next reload of an application will take longer without the serialized descriptor, an exception during serialization does not cause any functional problems, i.e., the application will still deploy fine.

    The above stacktrace made me suspicious, and I instrumented the insertProviderAt(), addProvider(), and removeProvider() methods of java.security.Security to dump the stack of the calling thread. My suspicion was confirmed: It turned out that the Jruby code that was bundled with the web application registered its own provider (named BC) for cryptographic (in particular, MessageDigest) services, but never unregistered it during undeployment!

    The following stack trace shows how the provider was registered by Jruby:

    
      at java.lang.Thread.dumpStack(Thread.java:1158)
      at java.security.Security.insertProviderAt(Security.java:329)
      at org.jruby.RubyDigest.createDigest(RubyDigest.java:50)
      at org.jruby.libraries.DigestLibrary.load(DigestLibrary.java:40)
      at org.jruby.runtime.load.LoadService.smartLoad(LoadService.java:277)
      at org.jruby.runtime.load.LoadService.require(LoadService.java:299)
      at org.jruby.RubyDigest.createDigestSHA2(RubyDigest.java:104)
      at org.jruby.libraries.DigestLibrary$SHA2.load(DigestLibrary.java:63)
      at org.jruby.runtime.load.LoadService.smartLoad(LoadService.java:277)
      at org.jruby.runtime.load.LoadService.require(LoadService.java:299)
      at org.jruby.RubyKernel.require(RubyKernel.java:670)
      at org.jruby.RubyKernelInvokerSrequire1.call(Unknown Source)
      at org.jruby.runtime.callback.InvocationCallback.execute(InvocationCallback.java:49)
      at org.jruby.internal.runtime.methods.FullFunctionCallbackMethod.internalCall(FullFunctionCallbackMethod.java:76)
      at org.jruby.internal.runtime.methods.DynamicMethod.call(DynamicMethod.java:69)
      at org.jruby.RubyObject.callMethod(RubyObject.java:487)
      at org.jruby.RubyObject.callMethod(RubyObject.java:395)
      at org.jruby.evaluator.EvaluationState.evalInternal(EvaluationState.java:770)
      at org.jruby.evaluator.EvaluationState.evalInternal(EvaluationState.java:298)
      at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:163)
      at org.jruby.evaluator.EvaluationState.evalInternal(EvaluationState.java:1317)
      at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:163)
      at org.jruby.RubyObject.eval(RubyObject.java:563)
      ....
    

    but this provider was never unregistered.

    You may wonder why the serialization of application descriptors during subsequent deployments would pick up a MessageDigest algorithm implementation from the cryptographic provider that was registered by Jruby, when the Jruby-based web application had already been undeployed, and why any attempt to acquire such a MessageDigest implementation would result in a ThreadDeath error.

    Here's an explanation: Jruby inserted its cryptographic provider at position 2 (using java.security.Security.insertProviderAt()), thereby preempting the default cryptographic provider for MessageDigest (and other algorithms) named sun.security.provider.Sun, which ships with Sun's JRE and which, by default, is registered in 2nd position, i.e., takes precedence over any providers registered with a higher position number (the lower the position number, the higher the priority during cryptographic algorithm implementation lookups for which no provider name was specified). (The Java runtime's list of cryptographic service providers and their precedence order is initialized from the runtime's lib/security/java.security file resource. Any changes to this list, which require appropriate security permissions, have VM-wide visibility.)

    Now, this would not have been an issue had Jruby unregistered its provider during the undeployment of its bundling web application. But it never did, causing the application descriptor serialization during the subsequent redeployment to acquire a MessageDigest algorithm implementation whose underlying classloader (an instance of org.apache.catalina.loader.WebappClassLoader) had been deactivated when the Jruby-based web application was undeployed. And was does a deactivated org.apache.catalina.loader.WebappClassLoader instance do when it is being asked to load a resource? It throws a ThreadDeath error!

    This also explains why the Jruby-based web application was unusable following its redeployment: The Jruby code ended up getting the same BC provider and its MessageDigest implementation that had been registered and loaded during the initial deployment, and whose classloader had been deactivated during undeployment, so any attempt to perform any digest operations by Jruby started failing in the redeployed version of the bundling web application.

    Lessons Learned:

    • Jruby should have unregistered its cryptographic provider during undeployment, by calling java.security.Security.removeProvider("BC"). This could have been done during the invocation of org.jruby.webapp.AbstractRailsServlet.destroy(). In addition, Jruby should not have registered its provider with such a high priority (using java.security.Security.insertProviderAt()). Instead, it should have registered its provider using java.security.Security.addProvider(), which inserts the provider at the next available position, which normally is at the end of the list, and should have requested its provider's MessageDigest algorithm implementation by passing its provider's name to the java.security.MessageDigest.getInstance() method.

    • The web container should have been more resilient against the case where a web application registers a cryptographic provider, but then forgets to unregister it during undeployment. In other words, the web container should have cleaned up after the Jruby-based web application (or any other web application for that matter) during its undeployment, by unregistering any of its cryptographic providers that were left behind. This can be accomplished by the following code:
      
          import java.security.*;
      
          Provider[] providers = Security.getProviders();
          if (providers != null) {
              for (Provider provider : providers) {
                  if (provider.getClass().getClassLoader() == this) {
                      Security.removeProvider(provider.getName());
                  }
              }
          }
      

      which has been added to org.apache.catalina.loader.WebappClassLoader.stop() and committed to GlassFish v2 Build 44.

Wednesday Mar 21, 2007

Alternate Docroots in Web Applications

Alternate Docroots in Web Applications

Alternate Docroots in Web Applications

    GlassFish v2 Build 40 adds support for alternate docroots to web applications. Previously, support for alternate docroots was limited to virtual servers.

    Alternate docroots allow web applications to serve requests for certain resources from outside their own docroot, based on whether those requests match one (or more) of the URI patterns of the web application's alternate docroots.

    This means that web applications no longer need to bundle all their resources. Instead, part of their resources may be outsourced into server directories outside the web application's docroot, so-called alternate docroots, where they may be shared with other web applications.

    If a request matches an alternate docroot's URI pattern, it will be mapped to the alternate docroot by appending the request URI (minus the web application's context root!) to the alternate docroot's physical location (directory).

    The URI patterns of alternate docroots of web applications support exact, path prefix, and extension matches, just like their counterparts for virtual servers. If a request matches multiple URI patterns, the alternate docroot is determined according to the following precedence order:

    1. Exact match
    2. Longest path match
    3. Extension match

    Alternate docroots of web applications are configured as sun-web.xml properties, using the same syntax as alternate docroots for virtual servers.

    Example: The following sun-web.xml specifies 3 alternate docroots:

    
    <sun-web-app>
      <property name="alternatedocroot_1" value="from=/my.jpg dir=/srv/images/jpg"/>
      <property name="alternatedocroot_2" value="from=*.jpg dir=/srv/images/jpg"/>
      <property name="alternatedocroot_3" value="from=/jpg/* dir=/src/images"/>
    </sun-web-app>
    

    The value of each alternate docroot has two name-value components: The first component (with name from) specifies the alternate docroot's URI pattern as its value, and the second component (with name dir) specifies the alternate docroot's physical location (directory) as its value.

    In this example, the URI pattern of the first alternate docroot uses an exact match, whereas the URI patterns of the 2nd and 3rd alternate docroots use extension and path prefix matches, respectively.

    Assume the above sun-web.xml belongs to a web application deployed at http://<host>:<port>/myapp.

    The first alternate docroot will cause any requests with this URL:

    
      http://<host>:<port>/myapp/my.jpg
    
    to be mapped to this resource:
    
      /svr/images/jpg/my.jpg
    

    while the 2nd alternate docroot will cause any requests with a *.jpg suffix, as in:

    
      http://<host>:<port>/myapp/*.jpg
    

    to be served from this physical location:

    
      /svr/images/jpg
    

    whereas the 3rd alternate docroot will cause any requests whose URI starts with /myapp/jpg/, as in:

    
      http://<host>:<port>/myapp/jpg/*
    

    to be served from the same directory as the 2nd alternate docroot.

    For example, the 2nd alternate docroot maps this request:

    
      http://<host>:<port>/myapp/abc/def/my.jpg
    

    to:

    
      /srv/images/jpg/abc/def/my.jpg
    

    and the 3rd alternate docroot maps:

    
      http://<host>:<port>/myapp/jpg/abc/resource
    

    to:

    
      /srv/images/jpg/abc/resource
    

    If a request does not match any of the target web application's alternate docroots, or if the target web application does not specify any alternate docroots, the request will be served from the web application's standard docroot, as usual.

Friday Mar 09, 2007

Access Log Patterns and Nicknames

Access Log Patterns and Nicknames

Access Log Patterns and Nicknames

    GlassFish v2 makes it easier to specify well-known access log patterns, by allowing you to specify access log patterns by their well-established nicknames.

    In GlassFish, you normally use the format attribute of the <access-log> subelement of <http-service> to specify the access log pattern of your virtual servers.

    The default access log pattern looks like this:

    
      format="%client.name% %auth-user-name% %datetime% %request% %status% %response.length%"
    
    

    The above are just a subset of the access log pattern tokens supported by GlassFish. The complete list of access log pattern tokens, from which you can assemble your own custom access log pattern, is as follows:

    
      %auth-user-name%
      %client.dns%
      %client.name%
      %cookie.value%
      %datetime%
      %header.accept%
      %header.%
      %header.auth%
      %header.date%
      %header.if-mod-since%
      %header.user-agent%
      %header.referer%
      %http-method%
      %http-uri%
      %http-version%
      %query-str%
      %referer%
      %request%
      %response.length%
      %status%
      %user.agent%
      %vs.id%
    
    

    Due to popular demand, GlassFish v2 supports shortcuts for well known access log patterns, allowing you to specify an access log pattern by its nickname, instead of having to list its constituent tokens.

    Currently, the following access log pattern nicknames are supported:

    Additional nicknames will be added in the future, based on the GlassFish community's input.

    So, in order to change the access log pattern from the GlassFish default to Apache's "common" format, all you need to do is issue the following asadmin command (or use the admin gui):

    
      asadmin set server.http-service.access-log.format="common"
    
    
    or to change it to Apache's "combined" format, issue this command:
    
      asadmin set server.http-service.access-log.format="combined"
    
    
    Provided that access logging has been enabled, the above commands will take effect immediately, without requiring a server restart.

Thursday Mar 08, 2007

Presentation on Virtual Hosting Features in GlassFish

Presentation on Virtual Hosting Features in GlassFish

Presentation on Virtual Hosting Features in GlassFish

Please see the slides of my presentation I gave at the GlassFish User Experience meeting on 03/07/2007, which covers the info from my related blog, plus recent updates.

Wednesday Dec 13, 2006

Virtual Hosting Features in GlassFish

Virtual Hosting Features in GlassFish

Virtual Hosting Features in GlassFish

  1. Introduction
  2. This document describes the virtual hosting features available in GlassFish v2.

    Virtual hosting refers to the ability to run several web sites (domains) from a single physical server machine with a single IP address. Each web site is identified by its domain (host) name. HTTP requests are routed to the appropriate domain based on their host name. In order for this name-based form of virtual hosting (also known as shared IP hosting) to work, the DNS must be configured in such a way that each hosted domain name resolves to the server's IP address. Virtual hosting enables ISP/ASP business models.

  3. Isolation levels
  4. In name-based virtual hosting, virtual servers are isolated by their host (domain) names. Each virtual server is assigned a unique host (domain) name, and may also be assigned one or more alias names. The web container will route a request to a virtual server based on the host name in the request URL.

    GlassFish provides one additional level of isolation between virtual servers: at the HTTP port level, which allows virtual servers to be assigned HTTP ports for their exclusive use.

    In GlassFish, it is possible to associate a virtual server with only a subset of the configured HTTP listeners: A virtual server will receive only those requests that were received on any of the HTTP listeners listed in its http-listeners attribute. As a result of this, virtual servers, or groups of them, may be isolated from each other at the HTTP port level by configuring their http-listeners attributes with mutually exclusive sets of HTTP listeners.

    Isolation at the HTTP port level is not supported by Tomcat, where a virtual server is always bound to all HTTP listeners that belong to the same <Service> unit as the <Engine> unit to which the virtual server belongs. (In Tomcat, a <Service> element represents the combination of one or more <Connector> components that share a single <Engine> component for processing incoming requests, where an <Engine> contains one or more <Host> children, each with one or more <Context> children.)

    Example:

    Consider the following request mapping table:

    Protocol

    Domain Name

    Port Number

    URL Prefix

    Target Virtual Server

    http

    mydomain.com

    1111

    http://mydomain.com:1111

    vs-1

    https

    mydomain.com

    2222

    https://mydomain.com:2222

    vs-1

    http

    yourdomain.com

    3333

    http://yourdomain.com:3333

    vs-2

    https

    yourdomain.com

    4444

    https://yourdomain.com:4444

    vs-2



    The above request mapping requirements, which isolate virtual servers vs-1 and vs-2 at both the domain name and HTTP port levels, can be configured very easily in GlassFish, by leveraging the http-listeners attribute of <virtual-server>, as follows:

    
      <http-listener id="http-listener-1" port="1111"/>
    
      <http-listener id="http-listener-2" port="2222" security-enabled="true">
        <ssl cert-nickname="nickname-1"/>
      </http-listener>
    
      <http-listener id="http-listener-3" port="3333"/>
    
      <http-listener id="http-listener-4" port="4444" security-enabled="true">
        <ssl cert-nickname="nickname-2"/>
      </http-listener>
    
      <virtual-server id="vs-1" hosts="mydomain.com"
                      http-listeners="http-listener-1,http-listener-2"/>
    
      <virtual-server id="vs-2" hosts="yourdomain.com"
                      http-listeners="http-listener-3,http-listener-4"/>
    

  5. Root context configuration choices
  6. A virtual server's root context is defined as the context with the empty path (/). Any requests that cannot be mapped to any of the web modules deployed on a virtual server are mapped to the virtual server's root context.

    The physical location of a virtual server's root context is given by the virtual server's docroot property. Each virtual server may be configured with its own individual docroot.

    In GlassFish, it is possible to map a virtual server's root context to one of the virtual server's web modules, by adding a default-web-module attribute to the virtual server's <virtual-server> entry and having its value reference the selected web module.

    A virtual server's default web module "shadows" the virtual server's docroot, with the effect that any requests that cannot be mapped to any of the web modules deployed on the virtual server will now be mapped to the virtual server's default web module instead of the virtual server's docroot. In other words, a web module that has been designated as a virtual server's default web module will serve any requests that match its context path plus any requests that map to the virtual server's root context.

    A virtual server's docroot remains shadowed for as long as the virtual server declares a default web module. Once a virtual server's default-web-module attribute has been removed, the virtual server's docroot will be reinstantiated as the virtual server's root context.

    The default-web-module mechanism is a convenient way for having a single web module occupy both its designated context path as well as the virtual server's root context.

  7. Alternate docroots
  8. In GlassFish, a virtual server may specify alternate docroots in addition to its main docroot. Each alternate docroot is associated with one or more URI patterns. An alternate docroot is selected over the main docroot if the request matches one of its URI patterns. Both path (prefix) and extension matches are supported.

    For example, it is possible to configure a virtual server in such a way that any requests (with an empty context path) whose URI starts with /images/* will be mapped to one alternate docroot, whereas any requests (with an empty context path) whose URI ends in *.jsp will be mapped to a different alternate docroot. All other requests (with an empty context path) will be mapped to the virtual server's main docroot.

    If a request matches multiple alternate docroot URI patterns, the following precedence order is used:

    • Exact match
    • Longest path match
    • Extension match

    Alternate docroots allow virtual servers to share a subset of their docroot resources.

    See my blog for additional details and examples.

  9. Security
    1. Single Sign On
    2. Single Sign On (SSO) enables web applications to share client authentication state. SSO is enabled at the virtual server level (and configured as a virtual server property). With SSO, a user who successfully authenticates to one web application becomes implicitly logged in to any other web application (deployed on the same virtual server) that shares the same authentication realm.

    3. Security realm
    4. In many cases, it is useful to configure a security realm at the virtual server level, with the expectation that this security realm apply to all web modules deployed on the virtual server.

      GlassFish supports this requirement, by defining a property named authRealm at the <virtual-server> level, whose value must be the name of an authentication realm declared in domain.xml.

      Standalone web modules (that is, those not bundled in an EAR file) inherit the realm referenced by the virtual server on which they are deployed, unless they specify a realm name in their web.xml deployment descriptor, which takes precedence.

      See my blog for additional details.

    5. Security credentials
    6. GlassFish allows HTTP(S) listeners to be assigned to a virtual server (or group of virtual servers) for its (their) exclusive use (see the earlier section that talks about isolation levels). Ideally, the HTTPS listeners assigned to one group of virtual servers will have their security credentials (i.e., private keys and supporting certificate chains) separated from the security credentials of a different set of HTTPS listeners serving a different group of virtual servers.

      GlassFish currently supports only a single keystore per server instance, which means the security credentials of all of the instance's HTTPS listeners must be stored in the same keystore, and may be differentiated by an alias name (cert-nickname) only. There has been an enhancement filed against GlassFish (see Issue 657) to support multiple key- and truststores per server instance.

  10. Dynamic reconfiguration
  11. Virtual servers, including their attributes and properties, are dynamically reconfigurable, that is, the creation or deletion of a virtual server, or any changes to its attributes or properties, do not require a server restart in order to take effect.

Wednesday Nov 29, 2006

New Support for Virtual Server Realms

New Support for Virtual Server Realms

New Support for Virtual Server Realms

In an ISP/ASP environment, it is often useful to be able to configure a security realm at the virtual server level, with the expectation that this security realm apply to all web applications deployed on the virtual server.

GlassFish v2-b26 supports this requirement, by introducing a new property named "authRealm" at the <virtual-server> level, whose value must be the name of an authentication realm declared in domain.xml.

Standalone web applications (that is, those not bundled in an EAR file) now inherit the realm referenced by the "authRealm" property (if any) of the virtual server on which they are deployed, unless they specify a realm in their web.xml deployment descriptor, which takes precedence.

Thursday Oct 05, 2006

New Support for Virtual Directory Mappings

New Support for Virtual Directory Mappings

New Support for Virtual Directory Mappings

In GlassFish, every virtual server has a docroot property, which points to the local directory that will be used to serve resources for requests that cannot be mapped to any of the web contexts deployed on the virtual server.

Alternatively, a virtual server may designate one of its deployed web modules as its default web module, by specifying a default-web-module attribute whose value refers to one of the deployed web modules by its name. In this case, any requests that cannot be mapped to any of the web modules deployed on the virtual server will be mapped to the virtual server's default web module.

By default, a virtual server's docroot points to the ${com.sun.aas.instanceRoot}/docroot directory, as can be seen from this domain.xml snippet:

  <virtual-server id="server" [...]>
    <property name="docroot" value="${com.sun.aas.instanceRoot}/docroot"/>
  </virtual-server>

This means that when you access this URL:

  http://<host>:<port>/index.html
the contents of the ${com.sun.aas.instanceRoot}/docroot/index.html resource will be served.

By default, all virtual servers share the same docroot directory, but since the docroot property is configurable, each virtual server may be configured with its own, individual docroot.

As of GlassFish v2-b20, it is possible to configure a virtual server's docroot with finer granularity, based on the URL pattern of the request.

For example, it is now possible to configure a virtual server so that all requests with a URI that starts with /images/* will be served from one directory, whereas all requests whose URI ends in *.jsp will be served from a different directory. Each mapping from URL pattern to directory path constitutes an alternate docroot.

In GlassFish v2, a virtual server's alternate docroots are configured using the <property> subelement of <virtual-server> (this is mainly for historical reasons), as in the following sample domain.xml snippet:

  <virtual-server id="server" [...]>
    <property name="alternatedocroot_1" value="from=/images/* dir=/usr/gifs"/>
    <property name="alternatedocroot_2" value="from=*.jpg dir=/usr/gifs"/>
    <property name="alternatedocroot_3" value="from=*.jsp dir=/usr/jsps"/>
    <property name="docroot" value="${com.sun.aas.instanceRoot}/docroot"/>
  </virtual-server>

A future version of GlassFish will add proper configuration support for alternate docroots by defining a <virtual-directory-mapping> element under <virtual-server> in domain.xml, with <local-path> and <url-pattern> subelements. The above configuration, while still supported, could then be represented more cleanly, as follows:

  <virtual-server id="server" [...]>
    <virtual-directory-mapping>
      <local-path>/usr/gifs</local-path>
      <url-pattern>/images/*</url-pattern>
      <url-pattern>*.jpg</url-pattern>
    </virtual-directory-mapping>
    <virtual-directory-mapping>
      <local-path>/usr/jsps</local-path>
      <url-pattern>*.jsp</url-pattern>
    </virtual-directory-mapping>
    <property name="docroot" value="${com.sun.aas.instanceRoot}/docroot"/>
  </virtual-server>

If a request matches multiple alternate docroot URL patterns, the following precedence order is used:

  1. Exact match
  2. Longest path match
  3. Extension match

Any request that cannot be mapped to any of the web modules deployed on the virtual server, and whose URI does not match any of the virtual server's alternate docroot URL patterns, will continue to be served from the directory indicated by the virtual server's docroot property.

You may use the GlassFish admingui or command-line interface (asadmin) to add alternatedocroot_ properties to a virtual server. Please note that GlassFish does not support multi-valued properties in domain.xml. Therefore, if you want to specify multiple alternate docroots for a given virtual server, you need to make sure that each alternate docroot has a distinct property name that starts with alternatedocroot_.

A future enhancement will be to support alternate docroots for web modules. This will, amongst other things, allow web modules to share certain kinds of resources available on the server, instead of requiring every web module to bundle such resources. For example, by each defining alternate docroots with URI patterns of the form /images/* and *.jpg and a target directory of /usr/gifs, web modules would be able to share any *.jpg resources available under /usr/gifs (provided they have read access to this directory).

Monday Aug 14, 2006

Jetty has integrated the JSP compiler from GlassFish

Jetty has integrated the JSP compiler from GlassFish

Jetty has integrated the JSP compiler from GlassFish

The folks at Jetty report that they have switched to leveraging the JSP compiler from GlassFish and are about to release with it in the next couple of days.

They mention the quality and stability of the GlassFish code, the support they've received from the GlassFish community during their transition, as well as the receptiveness of the GlassFish community to new ideas and contributions, as the main reasons for switching. See Jan Bartel's blog for details.

Congratulations to the Jetty folks, and welcome on board!

Calendar

Feeds

Search

Links

Navigation

Referers