Monday Mar 21, 2011

My GlassFish blogs

Even though Jan is no longer an active committer on GlassFish, he still care about it a lot and want to make sure that his blog entries, which have been proven very useful to the GlassFish community in the past, will be preserved.

Friday May 29, 2009

New sorting options for directory listings in GlassFish v3

New sorting options for directory listings in GlassFish v3

New sorting options for directory listings in GlassFish v3

The section in the Servlet specification that talks about Welcome Files has this:

If no matching welcome file is found in the manner described, the container may handle the request in a manner it finds suitable. For some configurations this may mean returning a directory listing or for others returning a 404 response.

GlassFish may be configured to return a directory listing if a request fails to be mapped to any welcome page, by setting the listings init parameter in the DefaultServlet declaration of the domain's default-web.xml to true, as follows:

  <servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
      <param-name>listings</param-name>
      <param-value>true</param-value>
    </init-param>
  </servlet>

Up until now, the directory listings were always sorted in alphabetical order.

The latest GlassFish v3 provides additional sorting options by last-modified date and size, by introducing a new init parameter with name sortedBy for the DefaultServlet, whose value may be set to one of NAME (the default), LAST_MODIFIED, or SIZE, as follows:

  <servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
      <param-name>listings</param-name>
      <param-value>true</param-value>
    </init-param>
    <init-param>
      <param-name>sortedBy</param-name>
      <param-value>[NAME|LAST_MODIFIED|SIZE]</param-value>
    </init-param>
  </servlet>

Please send any questions or comments you may have to the GlassFish webtier alias, or post them to the GlassFish webtier forum.

Thursday Dec 11, 2008

Changes to Servlet and Filter Registration APIs in Servlet 3.0

Changes to Servlet and Filter Registration APIs in Servlet 3.0

Changes to Servlet and Filter Registration APIs in Servlet 3.0

Right after the Public Review draft of the Servlet 3.0 specification had been handed off to the JCP for publication, the Servlet EG changed the signature of the addServlet and addFilter APIs on javax.servlet.ServletContext. These APIs are new in Servlet 3.0, and allow servlets and filters to be registered at runtime, without having to be declared in a web.xml deployment descriptor.

It was felt that the previous method signatures:

  addServlet(String servletName, String description, String className, Map initParameters, int loadOnStartup, boolean isAsyncSupported)

and

  addFilter(String filterName, String description, String className, Map initParameters, boolean isAsyncSupported)

had too many parameters, and there was concern that these methods would have to be overloaded every time a new parameter was added in the future.

Therefore, the Servlet EG decided to change addServlet to take just servletName and className parameters, and to change addFilter to take just filterName and className parameters, and to have these methods return ServletRegistration and FilterRegistration objects, respectively, on which any further configuration would occur.

For example, the following ServletContextListener registers one servlet and one filter when invoked at its contextInitialized method, and then further configures the registered servlet and filter through their respective ServletRegistration and FilterRegistration objects:

  public void contextInitialized(ServletContextEvent sce) {
      ServletContext sc = sce.getServletContext();

      /\*
       \* Register servlet
       \*/
      ServletRegistration sr = sc.addServlet("NewServlet", "test.NewServlet");
      sr.setInitParameter("servletInitName", "servletInitValue");
      sc.addServletMapping("NewServlet", new String[] {"/newServlet"});

      /\*
       \* Register filter that is going to intercept any requests to the
       \* servlet
       \*/
      FilterRegistration fr = sc.addFilter("NewFilter", "test.NewFilter");
      fr.setInitParameter("filterInitName", "filterInitValue");
      sc.addFilterMappingForServletNames("NewFilter",
          EnumSet.of(DispatcherType.REQUEST), true, "NewServlet");
  }

While these changes are not reflected in the Public Review draft of the Servlet 3.0 specification, they have already been implemented in the trunk version of GlassFish v3, which is going to be the reference implementation of the Servlet 3.0 specification.

Please send any questions or comments to the GlassFish webtier alias, or post them to the GlassFish webtier forum.

Monday Dec 08, 2008

How to downshift from HTTPS to HTTP in your web application

How to downshift from HTTPS to HTTP in your web application

How to downshift from HTTPS to HTTP in your web application

Problem description

Web applications will normally protect any of their web resources that process security-sensitive information (e.g., the login page used by FORM authentication) with SSL, to ensure that the information submitted by the user (e.g., username and password security credentials) will not be transmitted in the clear.

Some web applications will only protect web resources that process security-sensitive information in this manner, while allowing requests for other resources to travel in the clear.

Imagine a user accessing some of the unprotected resources of a web application (over HTTP) before accessing a resource that requires FORM authentication, where the FORM login page is guarded by a transport guarantee of CONFIDENTIAL according to the web application's deployment descriptor. In this case, the protocol will change from HTTP to HTTPS (to satisfy the transport guarantee of the login page), and will remain HTTPS even after the user has authenticated successfully and continues accessing any of the unprotected resources.

The fact that the protocol remains HTTPS once it has been changed to HTTPS, even though HTTPS may no longer be required, can be a problem for performance sensitive applications that do not want to incur the overhead of SSL unless required.

This blog explains how a web application can be written so that the protocol changes back to HTTP when HTTPS is no longer needed.

Solution

Imagine a very simple web application containing two servlets UseHttp.java and UseHttps.java, which are mapped to /useHttp and /useHttps, respectively. Further assume that the web application must satisfy the following requirements:

  • REQ_1: Access to "/useHttps" must be over HTTPS (otherwise, the servlet will throw an exception). This means that if the current protocol is HTTP, it must be upgraded to HTTPS.
  • REQ_2: Access to "/useHttp" must be over HTTP (otherwise, the servlet will throw an exception). This means that if the current protocol is HTTPS, it must be downshifted to HTTP.

REQ_1 will be handled automatically by the container, by declaring the following security constraint in the web application's web.xml:

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Protected resource</web-resource-name>
       <url-pattern>/useHttps</url-pattern>
       <http-method>GET</http-method>
    </web-resource-collection>
    <user-data-constraint>
      <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
  </security-constraint>

REQ_2 cannot be handled automatically by the container, and must be implemented by the application itself. In order to address REQ_2, we are going to implement a filter that intercepts every request and determines programmtically whether the target resource is guarded by a transport guarantee of CONFIDENTIAL. If it is not, and the request came in over HTTPS, the filter will issue a redirect to the target resource over HTTP.

In order to determine if a requested resource is guarded by HTTPS, the filter leverages the JavaTM Authorization Contract for Containers (Java ACC), which defines new java.security.Permission classes to satisfy the authorization model of the Java Platform, Enterprise Edition. In our particular case, the filter creates and uses an instance of javax.security.jacc.WebUserDataPermission to check if the target resource of a request is guarded by HTTPS.

The complete source code of the filter and the two servlets, as well as the web.xml, are provided as part of the sample application's WAR file and may be downloaded from this link.

For the sake of simplicity, we assume the default GlassFish configuration of three HTTP listeners: one listener reserved for any admin requests, a second listener for user traffic over HTTP (on port 8080), and a third listener for user traffic over HTTPS (on port 8181). (Notice that when you are using GlassFish v3 Prelude, you must enable the HTTPS listener on port 8181, which is disabled by default.)

The port number of the HTTP port for user requests is fed into the filter via an init parameter in web.xml:

  <filter>
    <filter-name>MyFilter</filter-name>
    <filter-class>MyFilter</filter-class>
    <init-param>
      <param-name>httpPort</param-name>
      <param-value>8080</param-value>
    </init-param>
  </filter>

This is necessary so that the filter will know which port number to use when redirecting from HTTPS to HTTP.

Demo

To see the upshift from HTTP to HTTPS, and downshift from HTTPS to HTTP in action, follow these simple steps:

  1. Download and install GlassFish v2 UR2 or GlassFish v3 Prelude.
  2. Download the sample web application sample.war, and deploy it to GlassFish.
  3. Access this URL: http://leah:8080/sample/useHttps. Notice how the request gets redirected to the HTTPS port, and how the protocol changes from HTTP to HTTPS: https://localhost:8181/sample/useHttps.
  4. Now edit the above HTTPS URL in your browser's address window by removing the trailing s from useHttps, so that the URL reads: https://localhost:8181/sample/useHttp, and hit return. Notice how the browser displays this warning message: You are about to leave an encrypted page. Information you send or receive from now on could easily be read by a third party, before issuing a redirect to the HTTP port and downshifting the protocol from HTTPS to HTTP: http://localhost:8080/sample/useHttp

I hope you found this blog useful. Please send any questions or comments you may have to the GlassFish webtier alias, or post them to the GlassFish webtier forum. I also would like to thank GlassFish security architect Ron Monzillo for his Java ACC related contribution to the idea behind this blog and to the filter code of the sample web application.

Thursday Oct 16, 2008

How to configure the security of HTTP session and Single-Sign-On cookies in GlassFish

How to configure the security of HTTP session and Single-Sign-On cookies in GlassFish

How to configure the security of HTTP session and Single-Sign-On cookies in GlassFish

Overview

The javax.servlet.http.Cookie API allows servlets to mark application specific cookies as secure by calling setSecure. However, there is no programmtic support for marking container-generated cookies, such as HTTP session (aka JSESSIONID) and Single-Sign-On (aka JSESSIONIDSSO) related cookies, as secure. This blog explains how the security aspects of these types of cookies may be configured in the upcoming GlassFish v2.1 and GlassFish v3 Prelude releases.

Motivation

The syntax for cookies, as given by the cookie specification, defines a Secure attribute (with no value). This attribute serves as an indication by the server to the client that the cookie contents require protection.

The client may determine what level of security it considers appropriate for cookies marked as secure. In most cases, it will send a secure cookie back to the server only if the connection is protected with SSL.

By default, a JSESSIONID cookie inherits the security setting of the request that initiated the corresponding HTTP session: If the HTTP session has been initiated by an HTTPS request, the secure attribute of its JSESSIONID cookie will be set to true, and will remain false if the session was initiated by a plain HTTP request.

In some cases, it may be necessary to override this default behaviour. For example, if the GlassFish instance is front-ended by an SSL-offloading loadbalancer (as shown in the image below), all requests received by it will be of type HTTP, whereas the traffic between the client and the loadbalancer is over HTTPS. In this case, any JSESSIONID cookies created by the web container on the GlassFish instance must be marked as secure (even though the corresponding sessions were initiated by HTTP requests), so that their contents (in particular the session ids) will be protected as they travel between the client and the loadbalancer.

Security configuration of HTTP session cookies

The upcoming GlassFish v2.1 and GlassFish v3 Prelude releases add support for configuring a JSESSIONID cookie's secure attribute through a cookie property with name cookieSecure in sun-web.xml, as follows:

  <?xml version="1.0" encoding="UTF-8"?>
  <sun-web-app>
    <session-config>
      <cookie-properties>
        <b><property name="cookieSecure" value="[true|false|dynamic]"/></b>
      </cookie-properties>
    </session-config>
  </sun-web-app>

The semantics of the possible values of the cookieSecure property are as follows.

  • true: Any JSESSIONID cookies created by the container on behalf of the web application will be marked as secure.
  • false: Any JSESSIONID cookies created by the container on behalf of the web application will be marked as non-secure.
  • dynamic: A JSESSIONID cookie created by the container on behalf of the web application will inherit its security setting from the request that initiated the correspoding session: If the session was initiated by an HTTPS request, its JSESSIONID cookie will be marked as secure, and will remain non-secure otherwise. This is the default.

Notice that the next version of the Servlet Specification (Servlet 3.0) is going to provide programmatic configuration support (including security) for JSESSIONID cookies by defining a new javax.servlet.SessionCookieConfig class along with a new setSessionCookieConfig method on javax.servlet.ServletContext which takes an argument of type javax.servlet.SessionCookieConfig. The configuration options provided by javax.servlet.SessionCookieConfig will be equivalent to the cookie-properties in sun-web.xml (with the addition of configuration support for the httpOnly cookie property).

Security configuration of Single-Sign-On cookies

Based on a user request, GlassFish v3 Prelude adds support for configuring the security of JSESSIONIDSSO cookies used in the context of Single-Sign-On (SSO). Since multiple applications may participate in SSO, and since only applications deployed to the same virtual server and belonging to the same security realm may participate in SSO, exposing the configuration of a JSESSIONIDSSO cookie's Secure attribute at the virtual server level seemed appropriate. Therefore, GlassFish v3 Prelude defines a new virtual-server property with name ssoCookieSecure, which may be set to true, false, or dynamic. The semantics are the same as described above for JSESSIONID cookies, except that in the case of dynamic (the default), the security setting of the JSESSIONIDSSO cookie is derived from the first request that initiated a session participating in SSO.

Please send any follow-up questions, or any questions on the GlassFish webtier in general, to webtier@glassfish.dev.java.net, or post them to the webtier forum.

Wednesday Oct 01, 2008

Retain session data during redeployment

Retain session data during redeployment

Retain session data during redeployment

Overview

The just-released GlassFish v3 Prelude provides a new feature that is going to become very popular with developers: the ability to retain HTTP session data across redeploys, which is something Jerome Dochez and I have worked on together.

Motivation

Let me briefly explain what motivated this feature, and why it is so important: When you redeploy a application, the application first gets undeployed, and then its new, updates bits are deployed. During the undeploy, GlassFish is going to remove any and all traces of the application, including its classloader, compiled JSPs, and file-persisted HTTP sessions (in case a file-based persistence mechanism is being used). In addition, any active HTTP sessions will be destroyed. In many cases, a number of HTTP sessions will have been created during each development cycle. After a redeployment, these sessions will have to be recreated from scratch: A tedious and time-consuming task!

How to enable

GlassFish v3 Prelude adds the ability to preserve any active HTTP sessions during a redeploy. A user may request this feature by passing the new keepSessions property (set to true) to the asadmin redeploy command.

Example usage:

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

The GlassFish admin console also exposes this new feature, which has also been incorporated into the Deploy on change feature of Netbeans, which instantly redeploys a WAR- or EAR-based, directory-exploded application as soon as one of its class files or descriptors has changed. Both Netbeans and the GlassFish Plugin for Eclipse allow developers to enable this feature by clicking on the new Preserve Sessions Across Redeployment checkbox, which can be seen at the bottom of the snapshots taken for NetBeans and GlassFish Plugin for Eclipse.

Implementation details

Behind the scenes, any active sessions will no longer be destroyed during a redeployment if so requested by the user: Instead, they will be stored in memory in serialized form, and restored from memory and deserialized when the redeployment has completed.

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 classes referenced by a session attribute may evolve only in a backwards compatible way.

Summary

GlassFish v3 Prelude simplifies your development cycles and increases your productivity by allowing you to preserve any active HTTP sessions when you redeploy your applications.

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.
About

jluehe

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today