12-Apr-06 (Created: 12-Apr-06) | More in '00.05-Articles'

Rewriting SendRedirect to deal with SSL (or https) offloading

Keywords

CSS
SSL offloader
sendRedirect problem
https becomes http
servlet filter
HttpServletResponseWrapper

Problem

When "sendRedirect" is used, some times the relative url is being translated into an absolute url using wrong scheme (http vs https). This articles explores the problem and a possible solution using servlet filters.

History

The call "sendRedirect" on an "HttpServletRequest" has had a history of being iffy and very dependent on the servlet container. About 4 to 5 years ago I have seen an issue where an incoming https when redirected using "sendredirect" will become an "http". The possible variations on a sendredirect forced me to abstract out the sendredirect into a pluggable interface with three implementations.

ClientsideRedirect
SendRedirect
ServersideRedirect

ClientsideRedirect

In this strategy the target url and the parameters are sent to the browser in a response. The response includes a javasript that will take the url and redirects the browser using document.location. It also removes the current url from the browser history so that the back button will work.

SendRedirect

Some times I call this a "defaultredirect" essentially preserving the "sendredirect" functionality where the browser is told to redirect using a redirect header.

ServersideRedirect

This strategy uses a "forward" of the servlet api there by including the response as part of the original url. Although this assures the fastest response time, has the draw back of repeating a "state" change on the server if a user were to refresh the page.

Why does "https" become "http" on a sendredirect?

Although a browser may be thinking that it is talking with its target server, there may be a number of servers enroute to the target servers doing such things as load balancing, encrypting, decrypting, etc. Usually this is not a problem. But there is case in which something called an "ssl offloader" is introduced into the pipeline of servers. These guys decode the "https" data and send the decoded data as "http" to the target server. At this point the target server thinks that the data is coming on an "http". Any sendredirect issued inside the server, especially relative urls, will be tagged with the protocol and the web server address. In this case the protocol will become "http".

Some web servers seem to deal with well, by recognizing that there is an offloader in the pipeline. This is typically done in association with the offloader where the offloader could be introducing an http header indicating that ssl offloading took place for a certain request.

Here is a symptom of this problem

first url from the browser


https://host:port/webapp/dir/page1.jsp

url by the time it reached the server


http://host:port/webapp/dir/page1.jsp

servelet redirects it to using sendredirect


/webapp/dir/page2.jsp

server sends a redirect to the browser by translating this to an absolute url


http://host:port/webapp/dir/page2.jsp

Notice how the "https" has become "http" in translating the relative url to an absolute url. This is because the webserver wrongly concluded that the incoming protocol is http.

How to fix the problem using webserver

if you are lucky and have enough time before production you can pursue with the vendor to see if they have fix for this in their web server.

Writing a sendredirect filter

Thanks to the servelet filtering architecture, it is surprisingly simple to fix it using a servelet filter. A servlet filter allows you to intercept all urls into a servlet container and alter the response related properties. The following code shows how easy it is to change the behavior of "sendredirect" for the whole web application while keeping its fundamental behavior intact.

Writing a basic filter


public class AbsoluteSendRedirectFilter implements Filter 
{
   public void init(FilterConfig filterConfig) 
   throws ServletException
   {
   }

   public void destroy()
   {   
   }

   public void doFilter(ServletRequest request
               , ServletResponse response
               , FilterChain chain)
   throws IOException, ServletException
   {
      //continue the request
      chain.doFilter(request,response);
   }
}

Registering a filter


<filter>
   <filter-name>AbsoluteSendRedirectFilter</filter-name>
   <display-name>AbsoluteSendRedirectFilter</display-name>
   <description>filter for transferring relative to absolute urls</description>
   <filter-class>AbsoluteSendRedirectFilter</filter-class>
   <init-param>
      <param-name>parm1</param-name>
      <param-value>parm1value</param-value>
      <description>description</description>
   </init-param>
</filter>

<filter-mapping>
   <filter-name>AbsoluteSendRedirectFilter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

Overriding the sendredirect


class SendRedirectOverloadedResponse extends HttpServletResponseWrapper
{
   private HttpServletRequest m_request;
   private String prefix = null;
   public SendRedirectOverloadedResponse(HttpServletRequest inRequest
      , HttpServletResponse response)
   {
      super(response);
      m_request = inRequest;
      prefix = getPrefix(inRequest);
   }
   
   public void sendRedirect(String location) throws IOException
   {
      Log.trace("Going originally to:" + location);
      String finalurl = null;
      
      if (isUrlAbsolute(location))
      {
         Log.trace("This url is absolute. No scheme changes will be attempted");
         finalurl = location;
      }
      else
      {
         finalurl = fixForScheme(prefix + location);
         Log.trace("Going to absolute url:" + finalurl);
      }
      super.sendRedirect(finalurl);
   }
   
   public boolean isUrlAbsolute(String url)
   {
      String lowercaseurl = url.toLowerCase();
      if (lowercaseurl.startsWith("http") == true)
      {
         return true;
      }
      else
      {
         return false;
      }
   }
   public String fixForScheme(String url)
   {
      //alter the url here if you were to change the scheme
      return url;
   }
   
   public String getPrefix(HttpServletRequest request)
   {
      StringBuffer str = request.getRequestURL();
      String url = str.toString();
      String uri = request.getRequestURI();
      Log.trace("requesturl:" + url);
      Log.trace("uri:" + uri);
      int offset = url.indexOf(uri);
      String prefix = url.substring(0,offset);
      Log.trace("prefix:" + prefix);
      return prefix;
   }
}

Putting it all together


public class AbsoluteSendRedirectFilter implements Filter 
{
   public void init(FilterConfig filterConfig) 
   throws ServletException
   {
   }

   public void destroy()
   {   
   }

   public void doFilter(ServletRequest request
               , ServletResponse response
               , FilterChain chain)
   throws IOException, ServletException
   {
      //continue the request
      chain.doFilter(request
         ,new SendRedirectOverloadedResponse(request,response));
   }
}

References

1. Java Server Side api ver 1.6

2. Look up just servlet package classes

3. Java Programming NOtes

>> Wednesday, April 12, 2006 1:54:49 PM - Comments by satya

Follow up

What is suggested here is not a comprehensive solution. A comprehensive solution perhaps take additional things into consideration as documented below.

See some additional notes on java.net on this topic.