Thursday, March 17, 2011

Removing context path for seam based applications

As stated earlier, a java web application behind any proxy is not aware that its context path is not required and tries to propagate it for redirects and links.

The solution I provided before by simply ignoring the context path when proxying to the application works, but only fights the symptoms. After spending some time with google and the seam and jsf documentation I found a way to get to the cause of the problem. It still does not cover all cases but works very well for seam based applications. For example, <s:link> now omits the context path while <h:outputLink> still preserves it. So the first workaround is still necessary but it doesn't hurt anyways.

The main idea is based on this post - use a Filter and a HttpServletResponseWrapper to modify the URL passed to all servlet responses by simply removing the context path. This magically applies also to all urls created by seam and leads to a more context-free url for your virtual host.

One thing you have to take care of is to set your cookiepath manually to keep cookies accessible whether your context path is active or not. For glassfish, you can do this in sun-web.xml:


    ...
    
        
            
        
    
    ...


Now without further comment the two required source files and the changes required for web.xml:

package eu.rbecker.util.servlet;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

/**
 *
 * @author Roben
 */
public class FakeContext extends HttpServletResponseWrapper {

    private String originalContext;

    public FakeContext(HttpServletResponse response, String originalContext) {
        super(response);
        this.originalContext = originalContext;
    }

    @Override
    public String encodeURL(String url) {
        // preserve original URL encoding
        String out = super.encodeURL(url);
        // remove original context path from url
        if (originalContext != null && !originalContext.isEmpty() && out.startsWith(originalContext)) {
            out = out.substring(originalContext.length());
        }
        return out;
    }
}

package eu.rbecker.util.filter;

import eu.rbecker.util.servlet.FakeContext;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author Roben
 */
public class MyResponseFilter implements Filter {

    private boolean omitContextPath = false;

    @Override
    public void init(FilterConfig fc) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest sr, ServletResponse sr1, FilterChain fc) throws IOException, ServletException {
        if (sr1 instanceof HttpServletResponse) {
            // create new wrapped response with fake context path
            FakeContext fake = new FakeContext((HttpServletResponse) sr1, ((HttpServletRequest) sr).getContextPath());
            // apply filter
            fc.doFilter(sr, fake);
        }
    }

    @Override
    public void destroy() {
    }
}


    myResponseFilter
    eu.rbecker.util.filter.MyResponseFilter


    myResponseFilter
    /*
    REQUEST
    FORWARD
    INCLUDE

Saturday, March 12, 2011

SASL PLAIN authentication failed: authentication failure

This error message in the mail.log file kept bugging me and prevented users from sending mails via smtp.

It took me some time to stumble upon auth.log, which presented a more detailed cause for this error:

Mar 12 14:33:02 pro1660 saslauthd[881]: auth_rimap: unexpected response to auth request: * OK [ALERT] Filesystem notification initialization error -- contact your mail administrator (check for configuration errors with the FAM/Gamin library)
Mar 12 14:33:02 pro1660 saslauthd[881]: do_auth         : auth failure: [user=testbox@myserver.de] [service=smtp] [realm=myserver.de] [mech=rimap] [reason=[ALERT] Unexpected response from remote authentication server]

As stated by blog.desgrange.net, "a quick solution to solve that is to replace FAM with Gamin":

apt-get install libgamin0

This should also automatically remove libfam0 which was causing the error.

Thursday, March 10, 2011

Glassfish v2 behind Apache mod_proxy with SSL and dynamic virtual hosts

This post is about how to configure Glassfish behind an Apache 2 using mod_proxy while allowing SSL-connections. The glassfish server runs several applications within their own context paths. Besides that, it has no requirements for own virtual hosts or SSL or other magic you can think of, which keeps the glassfish setup plain and simple.

As for the apache part, in my case each application should be accessed via its own domain, so a (dynamic) virtual host setup is required. Also, the applications should each be reachable via https and plain old http.

My configuration is based on this post, so some of the following parts should be equal. Also, the requirements are nearly the same:
  • Apache 2.2.x
  • SSL
  • mod_proxy
  • mod_rewrite
  • Glassfish 1.x+, 2.x or 3.x (with adjustments)
  • A multi-domain certificate for all virtual hosts. This allows us to have multiple ssl-hosts on one single IP adress. Google has more information on this topic.
  • Know how to setup your apache for ssl, since this will not be handled explicitly here. If not, again, google is your friend.
Everything in place? Here we go!

1) I recommend using a dedicated directory for the two proxy-specific configuration files we will create. I used /etc/apache2/gf/ for this task and will reference it for this tutorial.

2) In this directory create two files: hosts.map, where we will store hostname-application mappings and proxy.conf, where... you can guess this by yourself.

The first file, hosts.map, is a simple key-value map like this:
gfadmin.mydomain.de 8048/
app1.myserver.de 8080/app1
app2domain.de 8080/app2

The left column defines all hostnames we proxy forward to the glassfish-contexts (read: applications) defined in the right column. The number before is simply the port your glassfish runs on.

The first pair is to access the glassfish administration console via its own fancy hostname. This is not required but I thought nice to have.

The second file contains the real magic:
# enable rewrite engine
RewriteEngine           on

# pass the Host: line from the incoming request to the proxied host
ProxyPreserveHost       on

# define the mapping file
RewriteMap vhost txt:/etc/apache2/gf/hosts.map

# store client ip in request header
RequestHeader Set Proxy-ip %{REMOTE_ADDR}e

# add missing / for empty urls:
RewriteRule ^$ / [R,L]

# This rule fixes some browsers (safari) adding the port to the host name:
RewriteCond %{HTTP_HOST} ^([^/:]*)(:(80|443))?

# proxy-redirect with map lookup based on HTTP_HOST variable with ports removed and localhost:8080/badhost.html as default
# replace 8080/badhost.html with anything you want to throw at people accessing hosts not defined in your hosts map.
# note, that other standard apache vhosts still work so you don't need to handle them here!
RewriteRule ^/(app1/|app2/)?(.*) http://localhost:${vhost:%1|8080/badhost.html?}/$2 [P,L]

Anoyingly, you have to exclude the context paths from being forwarded to the application server as the applications always try to add it to automatically created hyperlinks. This is what the (app1/|app2/)? at the beginning of the RewriteRule is about. You can use any other regexp matching your context paths here, in this example (app\d/)? should work equally. I did not yet find a better solution for this issue. Other options are to change the links in your applications to omit the context path or to use virtual hosts also for your glassfish and give each application a root-context (/), which is what I wanted to avoid in the first place.

3) Now we only need to create a default virtual host for this machine. A default virtual host serves as catch all for requests not handled via explicit <VirtualHost> sections containing a SeverName or ServerAlias directive. As for ssl, you can only have one virtual host since the Request-URL can only be determined *after* the certificate is defined. So basically, if you only have one single IP-address and don't want to use different ports, you can only use one certificate and ssl-vhost for your server. This is also why we need a multi domain certificate for this setup.

Back to topic - You can simply discard your default virtual host configuration (000-default) and replace it with this one:
<VirtualHost *:443>
        # ssl
        SSLEngine on

        # certificates
        SSLCertificateKeyFile /etc/apache2/cert/server.key
        SSLCertificateFile /etc/apache2/cert/certificate.crt
        SSLCertificateChainFile /etc/apache2/cert/chainFile.crt

        # this i just took over from the original post. I guess it has its reason standing there...
        RequestHeader Set Proxy-keysize 512

        Include /etc/apache2/gf/proxy.conf
</VirtualHost>
<VirtualHost *:80>
        Include /etc/apache2/gf/proxy.conf
</VirtualHost>

4) Now your apache configuration is done. The last thing to be done is to adjust your glassfish http-listeners. Add the following attributes to the default listener (default is http-listener-1) and the admin-listener if you proxy to this one too:
key: authPassthroughEnabled
value: true

key: proxyHandler
value: com.sun.enterprise.web.ProxyHandlerImpl

I (and the glassfish documentation) recommend setting the listeners to listen only to localhost because we assumingly created some kind of security risk with the above settings. Simply set the field "Network Address" to 127.0.0.1. You can also disable the https listener (http-listener-2) since the apache takes care of ssl.

5) Restart or reload glassfish and apache and everything should work.

Hello world

Every blog starts with a first post, so does this one, too.

I will use it as a knowledge base for me and hopefully others or just to tell the world about things I think I have to share. So, without further words, I will start with a very technical post which was mainly the reason for me to finally sign up here.

So, have fun or inspiration or anything else a blog could serve and feel free to leave a comment!