Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-6588270

Network: Applet Cache delivers disregards Http Header "Expires:0"

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Won't Fix
    • Icon: P4 P4
    • tbd
    • 6
    • deploy
    • x86
    • windows_xp

      FULL PRODUCT VERSION :
      Java Plug-in 1.6.0_01

      Verwendung der JRE-Version 1.6.0_01 Java HotSpot(TM) Client VM

      ADDITIONAL OS VERSION INFORMATION :
      Microsoft Windows XP [Version 5.1.2600]

      A DESCRIPTION OF THE PROBLEM :
      When an HTTP response contains the following Headers:

      Expires:0
      Last-Modified: [current date]

      Nevertheless, the response is put to the plugin cache and subsequent requests for the same resourcse are answered from that cache.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      0.) Create Source files from the provided source code (use markers to identify borders)

      SimpleServer.java
      PluginCacheTest.java
      plugin_cache_test.html

      1. Compile the Java Source files

      2. Start the SimpleServer (assumes SimpleServer.class being in classpath)
      java SimpleServer

      3. Place the file PluginCacheTest.class to {user.home}

      4. Specify your_ip in the file plugin_cache_test.html

      5. Activate and clean your plugin (Applet) cache in the Java Control Panel

      6. Load plugin_cache_test.html in a browser that uses the Java 6 Plugin



      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The expected result is, that the second request for /zeroexpires.txt would not be delivered from cache. This would result in a different random value in the output (see actual result).
      ACTUAL -
      Content of initial zeroexpires.txt - delivers a random value

      encoding:null, contentLength:18, expires:0(0)

      0.6473260046689359

      Content of subsequent zeroexpires.txt - if the value is equal to the initial request, the cache delivered the item

      encoding:null, contentLength:18, expires:0(0)

      0.6473260046689359

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      ####################################### SimpleServer.java ###########
      import java.io.*;
      import java.nio.channels.*;
      import java.net.*;
      import java.util.*;
      import java.util.zip.GZIPOutputStream;
      import java.text.*;


      /**
       * Very minimalistic HTTP Server which servers 3 requests
       *
       * a) the zeroexpires.txt which contains an "Expires:0" response header
       * b) the gzipped.txt which can be delivered gzipped/not gzipped
       * c) the test Applet
       *
       * Note that this Program assumes the Applet's class file (PluginCacheTest.class)
       * in $user.home
       *
       * @author Jan Kaiser <jan dot kaiser at interactivedata dot com>
       */
      public class SimpleServer
      {
        static int buf_current;
        static int buf_header_end;
        static byte[] buf = new byte[4096];
        static SimpleDateFormat expiresFormatter =
            new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
        
        public static void main(String[] args)
        {
          expiresFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
          try
          {
            ServerSocket mySocket = new ServerSocket(8080);
            while(true)
            {
              buf_current = 0;
              buf_header_end = 0;
              buf = new byte[4096];

              Socket clientSocket = mySocket.accept();
              System.out.println("New connection accepted " +
                  clientSocket.getInetAddress() + ":" + clientSocket.getPort());
              OutputStream out = clientSocket.getOutputStream();
              InputStream in = clientSocket.getInputStream();
              while ((buf_header_end = findEndOfHeader(buf, 0, buf_current)) < 0)
              {
                buf_current += do_read(buf, buf_current, (buf.length - buf_current) - 1, in);
                if (buf_current == buf.length)
                {
                  throw new IOException("Header too long");
                }
              }
              String header = new String(buf, 0, buf_header_end);
              StringTokenizer tokens = new StringTokenizer(header,"\r\n");
              String query = null;
              Hashtable headers = new Hashtable();
              while (tokens.hasMoreTokens())
              {
                String headerToken = tokens.nextToken();
                int index = -1;
                if ((index = headerToken.indexOf(":")) > -1)
                {
                  String name = headerToken.substring(0,index);
                  String value = headerToken.substring(index+1,headerToken.length());
                  value = value.trim();
                  System.out.println("header:" + name + ":" + value + " from " + headerToken);
                  headers.put(name, value);
                }
                else // request
                {
                  System.out.println("Query: " + headerToken);
                  StringTokenizer queryTokens = new StringTokenizer(headerToken, " ");
                  while (queryTokens.hasMoreTokens())
                  {
                    String queryToken = queryTokens.nextToken();
                    if (queryToken.startsWith("/"))
                    {
                      query = queryToken;
                      break;
                    }
                  }
                }
              }
              if (query == null)
              {
                throw new IOException("No query available");
              }
              if (headers.size() == 0)
              {
                throw new IOException("No Headers");
              }
              // read the request body if there is some
              if (headers.containsKey("ContentLength"))
              {
                int queryContentLength = Integer.parseInt((String)headers.get("Content-Length"));
                readBody(queryContentLength, in);
              }
              String output = null;

              // delivery of the zeroexpires content
              if (query.equals("/zeroexpires.txt"))
              {
                String content = String.valueOf(Math.random());
                output = "HTTP/1.0 200 OK\r\n" +
                         "Content-Length: "+ content.length() +"\r\n" +
                         "Content-Type: text/plain\r\n" +
                         "Last-Modified: Wed, 09 May 2007 08:13:10 GMT\r\n" +
                         "Expires: 0\r\n" +
                         "\r\n" +
                             content;
                out.write(output.getBytes());
              }
              // delivery of the Applet itself
              else if (query.indexOf("/PluginCacheTest") != -1)
              {
                
                FileInputStream fileStream = new FileInputStream(System.getProperty("user.home") + "/PluginCacheTest.class");
                FileChannel channel = fileStream.getChannel();
                long size = channel.size();
                output = "HTTP/1.0 200 OK \r\n" +
                         "Content-Type: application/octet-stream\r\n" +
                         "Content-Length: " + size + "\r\n" +
                         "\r\n";
                out.write(output.getBytes());
                int read = -1;
                while ((read = fileStream.read()) != -1)
                {
                  try
                  {
                    out.write(read);
                  }
                  catch(Exception ex)
                  {
                    System.out.println("Caught exception when trying to write on out");
                  }
                }
              }
              else
              {
                String content = query + " Not Found";
                output = "HTTP/1.0 404 Not Found\r\n" +
                          "Content-Length: " + content.length() + "\r\n" +
                          "\r\n" +
                          content;
                out.write(output.getBytes());
              }
              out.flush();
              out.close();
            }
          }
          catch(Exception ex)
          {
            System.out.println("Exception in main " + ex.getMessage());
            ex.printStackTrace();
          }
        }
        
        static int do_read(byte[] buf, int start, int length, InputStream input) throws IOException
        {
          int have_read = -1;
          try
          {
            have_read = input.read(buf, start, length);
          }
          catch (InterruptedIOException ex)
          {
            throw new IOException("ReadTimeout");
          }
          if (have_read < 0)
          {
            throw new IOException("ServerSideClose");
          }
          else if (have_read == 0)
          {
            throw new IOException("ZeroBytesRead");
          }
          return have_read;
        }
        
        static byte[] do_read(byte[] buf, int current_body_length, InputStream i) throws IOException
        {
          int have_read = -1;
          byte[] ret;
          try
          {
            while (true)
            {
              have_read = i.read(buf, current_body_length, buf.length-current_body_length);
              if (have_read >= 0)
              {
                if (have_read == (buf.length-current_body_length))
                {
                  byte[] newBuf = new byte[2*buf.length];
                  System.arraycopy(buf, 0, newBuf, 0, buf.length);
                  buf = newBuf;
                }
                current_body_length += have_read;
              }
              else
              {
                break;
              }
            }
            ret = new byte[current_body_length];
            System.arraycopy(buf, 0, ret, 0, current_body_length);
          }
          catch (InterruptedIOException ex)
          {
            throw new IOException("ReadTimeout");
          }
          return ret;
        }
        
        static int findEndOfHeader(byte[] buf, int start, int length)
        {
          if (length < 2)
          {
            return -1;
          }
          int index = -1;
          if ((index = new String(buf, start, length).indexOf("\r\n\r\n")) > -1)
          {
            return index;
          }
          return -1;
        }
        
        static byte[] readBody(int contentLength, InputStream input) throws IOException
        {
          byte[] body = new byte[contentLength];
          int current_body_length = 0;
          if (buf_header_end < buf_current) // read more than header
          {
            current_body_length = buf_current - buf_header_end;
            if (current_body_length > contentLength)
            {
              current_body_length = contentLength;
              System.arraycopy(buf, buf_header_end, body, 0, current_body_length); // copy current_body_length
              int need_to_shift = buf_current - (buf_header_end + current_body_length);
              System.arraycopy(buf, buf_current - need_to_shift, buf, 0, need_to_shift);
              buf_current = need_to_shift;
            }
            else
            {
              System.arraycopy(buf, buf_header_end, body, 0, current_body_length);
              buf_current = 0;
            }
          }
          else
          {
            buf_current = 0;
          }
          buf_header_end = -1;
          return getBody(current_body_length, contentLength, body, input);
        }
        
        static byte[] getBody(int current_body_length, int contentLength, byte[] body, InputStream input)
        throws IOException
        {
          while (current_body_length < contentLength) // check wheter there is more to read
          {
            current_body_length += do_read(body, current_body_length, contentLength - current_body_length, input);
          }
          return body;
        }
      }


      #################################### PluginCacheTest.java ###########

      import java.applet.*;
      import java.net.*;
      import java.io.*;
      import java.util.*;
      import java.util.zip.*;

      /**
       * This applet reproduces the following behaviour of the plugin cache in java 6:
       * - items that are delivered with the HTTP headers "Expires: 0" and "Last-Modified: [now]" are written in the cache and delivered
       * from the cache on subsequent requests for the items
       * - items that are requested with "Accept-Encoding: gzip" are delivered correctly the first time they are requested.
       * On subsequent requests, these items are delivered from the Plugin Cache with the header "Content-Encoding:gzip" and
       * "Content-Length: [gzipped-content-length]", but with decompressed content, so that the client tries to read the
       * content having a length of gzipped-content-length and regards the content as gzipped, but can neither read the full content
       * (because the content length is not correct) nor apply any gzip depcompression (because the content is not gzipped)
       *
       * This applet assumes the SimpleServer program running on its codebase.
       *
       * @author Jan Kaiser <jan dot kaiser at interactivedata dot com>
       */
      public class PluginCacheTest extends Applet
      {
        public void start()
        {
          try
          {
            String url = getCodeBase() + "zeroexpires.txt";
            System.out.println("Content of initial zeroexpires.txt - delivers a random value");
            getContent(url, null);
            Thread.sleep(5000);
            System.out.println("Content of subsequent zeroexpires.txt - if the value is equal to the initial request, the cache delivered the item");
            getContent(url, null);
          }
          catch(Exception ex)
          {
            reportException(ex);
          }
        }

        private void getContent(String url, Hashtable headers) throws Exception
        {
          URL u = new URL(url);
          URLConnection conn = u.openConnection();
          if (headers != null)
          {
            Enumeration keys = headers.keys();
            while (keys.hasMoreElements())
            {
              String key = (String)keys.nextElement();
              String value = (String)headers.get(key);
              conn.setRequestProperty(key, value);
            }
          }
          InputStream is = conn.getInputStream();
          boolean gzip = false;
          String encoding = conn.getContentEncoding();
          int contentLength = conn.getContentLength();
          String expires = conn.getHeaderField("Expires");
          long expiration = conn.getExpiration();
          System.out.println("encoding:"+encoding+", contentLength:"+contentLength+", expires:" + expires+"("+expiration+")");
          if (encoding != null && encoding.equals("gzip"))
          {
            gzip = true;
          }
          readStream(is, gzip);
        }

        private void readStream(InputStream is, boolean gzip) throws IOException
        {
          InputStream input = null;
          if (gzip)
          {
            try
            {
              input = new GZIPInputStream(is);
            }
            catch(Exception ex)
            {
              System.out.println("Could not create GZIPInputStream, using normal InputStream: " + ex.getMessage());
              input = is;
            }
          }
          else
          {
            input = is;
          }
          int c = -1;
          while ((c = input.read()) != -1)
          {
            System.out.print((char)c);
          }
          System.out.print("\n");
          input.close();
        }

        private void reportException(Exception ex)
        {
          System.out.println("caught exception");
          ex.printStackTrace();
        }
      }

      ############################### plugin_cache_test.html #########


      <html>

      <head>

      <title>Java 6 - the story continues ...</title>

      </head>

      <body>

      <applet code="PluginCacheTest" codebase="http://your_ip:8080/"></applet>

      </body>

      </html>


      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      1) Disable the Plugin Cache (via Java Control Panel)

      2) Use HTTP Headers

      Pragma: no-cache
      Cache-Control: no-cache

      (which unfortunately disables caching on further intermediate caches, such as proxy servers)

            almatvee Alexander Matveev
            ryeung Roger Yeung (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: