Take control of the servlet environment, Part 3

Beware of the cookie monster

1 2 Page 2
Page 2 of 2

But never fear, there's still hope. A minor modification to the session-to-cookie transmogrification will buy you some breathing room. Instead of placing each session value into its own cookie, you can put all the session values in a single cookie. How? Store all the session data in a hash table and then serialize the hash table to a single cookie when the RSEF framework calls the SessionWrapper.save() method. Likewise, when the session wrapper is instantiated, it deserializes the hash table from the existing cookie, if present. The net.rudiment.servlet.session.cookie.!aggregate.SessionWrapper module completes this entire process. The logic simply recapitulates techniques already discussed, so we will spare you the code dissection.

But it's not perfect ... yet!

Just when you thought it was safe, the cookie monster resurfaces. You beat the limit on the number of cookies, but now you're going to break the limit for the size of cookies. In regard to a cookie's data size, 1,024 characters seems to be the lowest common denominator. Once your serialized hash table exceeds this limit, your luck runs out. Will you ever see the light at the end of the tunnel? Yes, read on!

The solution to this problem, and coincidentally the reason why we penned this series of articles, is the following class: net.rudiment.servlet.session.cookie.aggregate.crumbled.SessionWrapper. Yes, it's quite a mouthful! With this class, we've combined the best of both worlds: you still store the session in a serialized hash table, and you still store the hash table in the cookie. But if the serialized hash table exceeds the size limit, you can break it into smaller chunks and write each chunk to a different cookie. Thus, our patent-pending, world-famous "crumbled cookie" session wrapper.

To create the crumbled cookie -- the chunked serialization of the hash table -- serialize the hash table into a string and then slice the string into bite-sized pieces. (Will the cookie puns ever end?) The following method translates the in-memory session data into the serialized string:

    protected String aggregate()
    {
        Hashtable hash = new Hashtable();
        String[] names = super.getValueNames();
        for( int x = 0; x < names.length; x++ )
        {
            String name = names[x];
            Object value = super.getValue( name );
            hash.put( name, value );
        }
        return( Serialize.objectToString( hash ) );
    }

The save() method calls aggregate() and then fragments the string if necessary. Note that you prepend a number to each cookie name so that you can reassemble them in order:

    public void save()
    {
        if( null != _response )
        {
            String serialized = aggregate();
            if( ( serialized == null ) || ( serialized.trim().equals( "" )
) )
            {
                throw( new RuntimeException( "Possible UNserializable
object placed in session!" ) );
            }
            int numberOfFragments = ( serialized.length() /
MAX_COOKIE_SIZE ) + 1;
            for ( int i = 0; i < numberOfFragments; i++ )
            {
                Cookie cookie = new Cookie( prefix + i, extractFragment(
serialized, i ) );
                cookie.setMaxAge( -1 );
                _response.addCookie( cookie );
            }
        }
    }

Simple so far. But loading the serialized session back into memory can further complicate the process. Keep an eye out for the plethora of pitfalls to avoid: Does the cookie contain a serialized session? If so, is the session fragmented? If so, how many fragments are there? Are any fragments missing? Are there extra fragments from older (and larger) serialized sessions? Confused yet? We sure were. It took us a while to perfect this loading technique.

The load() method first iterates all the cookies, picks out the ones that relate to your serialized session, and stores them in a temporary hash table:

    protected void load()
    {
        if( null != _request )
        {
            Cookie[] cookies = _request.getCookies();
            if( null != cookies )
            {
                Hashtable fragments = new Hashtable();
                for( int x = 0; x < cookies.length; x++ )
                {
                    String name = cookies[x].getName();
                    // if( null == name )?
                    if( name.startsWith( prefix ) )
                    {
                        String value = cookies[x].getValue();
                        if( null != value )
                        {
                            fragments.put(
                                determinePosition( name ),
                                value );
                        }
                    }
                }

Once we have collected the fragments, we can assemble them and deserialize the hash table:

                try
                {
                    String serialized = assemble( fragments );
                    if( null == serialized )
                    {
                        invalidate();
                    }
                    else
                    {
                        if ( ! serialized.trim().equals( "" ) )
                        {
                            disect( serialized );
                        }
                        else
                        {
                            // No cookie to restore (first visit?)
                        }
                    }
                }
                catch( NullPointerException e )
                {
                    e.printStackTrace();
                    invalidate();
                }
            }
        }
    }

The assemble() method referenced above concatenates the serialized fragments in the proper order:

    private String assemble( Hashtable table )
    {
        StringBuffer sb = new StringBuffer();
        String value;
        for ( int i = 0; i < table.size(); i++ )
        {
            value = (String) table.get( new Integer( i ) );
            if ( value == null )
            {
                System.err.println( new Exception( "Missing fragment for
session, expecting " + i ) );
                return null;
            }
            else
            {
                sb.append( value );
            }
        }
        return sb.toString();
    }

If you successfully locate and load a serialized session, the disect() method then deserializes it and passes the data to the engine:

    protected void disect( String serialized )
    {
        Hashtable hash = (Hashtable) Serialize.objectFromString(
serialized );
        if( ( hash != null ) && ( ! "null".equals( hash ) ) )
        {
            Enumeration e = hash.keys();
            while( e.hasMoreElements() )
            {
                String name = (String) e.nextElement();
                Object value = hash.get( name );
                super.putValue( name, value );
            }
        }
        else // session is corrupt!
        {
            invalidate();
        }
    }

This hybrid solution, combining the one-to-one session-cookie solution and the one-big-cookie solution, affords the best of both worlds. This solution is, in fact, what we use in our production environment. It works beautifully, and now it's all yours. Enjoy.

Conclusion

As we have demonstrated in these articles, the RSEF allows you to take control of the servlet environment in an unobtrusive manner. Benefits include configurable session management and the concealment of shortcomings like the cookie subdomain issue. Use the framework, extend it, improve it, and share your work. We're always interested in seeing what you can do with it.

Thomas E. Davis is a Sun Certified Java Developer and the chief technology officer of his second successful Internet-related company. In addition to being a Java advocate, Thomas is a strong proponent of the extreme programming, design patterns, and refactoring philosophies, which he preaches to his colleagues and supports within his own department. Thomas welcomes constructive criticism and intelligent comments in relation to his articles. Craig Walker is a Sun Certified Java Programmer (awaiting the results of his Developer exam) and senior software engineer. A former consultant to IBM, working on its internal Java initiatives, Craig has pursued the mastery of many Java APIs ranging from the presentation layer to the enterprise.

Learn more about this topic

  • Books

1 2 Page 2
Page 2 of 2