[grizzly-sendfile~main:283] adding support for persistent connections (HTTP keep-alive)

  • From: igor_minar@kenai.com
  • To: commits@grizzly-sendfile.kenai.com
  • Subject: [grizzly-sendfile~main:283] adding support for persistent connections (HTTP keep-alive)
  • Date: Mon, 25 Jan 2010 18:48:21 +0000

Project:    grizzly-sendfile
Repository: main
Revision:   283
Author:     igor_minar
Date:       2010-01-21 09:50:36 UTC
Link:       

Log Message:
------------
SendfileStats - don't decrement registeredDownloads value for downloads that 
don't get registered (HTTP 304)
SendfileFilter - prevent grizzly from closing connections prematurely when 
adapter is taking a long time to return (e.g. file upload)
adding support for persistent connections (HTTP keep-alive)


Revisions:
----------
281
282
283


Modified Paths:
---------------
sendfile-core/src/main/java/com/igorminar/grizzlysendfile/jmx/SendfileStats.java
grizzly-sendfile-g19/src/main/java/com/igorminar/grizzlysendfile/SendfileFilter.java
sendfile-core/src/main/java/com/igorminar/grizzlysendfile/Download.java
sendfile-core/src/main/java/com/igorminar/grizzlysendfile/SendfileSelectorThread.java
sendfile-core/src/main/java/com/igorminar/grizzlysendfile/SendfileTask.java
sendfile-core/src/test/java/com/igorminar/grizzlysendfile/SendfileSelectorThreadTest.java
sendfile-test/src/main/java/com/igorminar/grizzlysendfile/AbstractIntegrationTest.java


Added Paths:
------------
grizzly-sendfile-g19/src/main/java/com/igorminar/grizzlysendfile/KeepAliveHandler.java
grizzly-sendfile-g19/src/test/java/com/igorminar/grizzlysendfile/Grizzly19KeepAliveTest.java
sendfile-core/src/main/java/com/igorminar/grizzlysendfile/ChannelClosureHandler.java
sendfile-core/src/test/java/com/igorminar/grizzlysendfile/DefaultChannelClosureHandlerTest.java
sendfile-test/src/main/java/com/igorminar/grizzlysendfile/AbstractKeepAliveTest.java
sendfile-test/src/main/java/com/igorminar/grizzlysendfile/AbstractSendfileTest.java


Diffs:
------
diff -r a68e8b200193 -r a41a1b18bad3 
sendfile-core/src/main/java/com/igorminar/grizzlysendfile/jmx/SendfileStats.java
--- 
a/sendfile-core/src/main/java/com/igorminar/grizzlysendfile/jmx/SendfileStats.java
  Wed Jan 13 20:55:40 2010 -0800
+++ 
b/sendfile-core/src/main/java/com/igorminar/grizzlysendfile/jmx/SendfileStats.java
  Fri Jan 15 12:49:04 2010 -0800
@@ -90,9 +90,8 @@
     synchronized void addDownloadResult(int downloadResult,
                                         long transferredBytes,
                                         boolean wasInProgress) {
-        registeredDownloads--;
-
         if (wasInProgress) {
+            registeredDownloads--;
             downloadsInProgress--;
         }
         


diff -r a41a1b18bad3 -r 7c64e73e02d9 
grizzly-sendfile-g19/src/main/java/com/igorminar/grizzlysendfile/SendfileFilter.java
--- 
a/grizzly-sendfile-g19/src/main/java/com/igorminar/grizzlysendfile/SendfileFilter.java
      Fri Jan 15 12:49:04 2010 -0800
+++ 
b/grizzly-sendfile-g19/src/main/java/com/igorminar/grizzlysendfile/SendfileFilter.java
      Thu Jan 21 01:46:43 2010 -0800
@@ -152,11 +152,26 @@
             
         } else {
 
+            Object attachment = task.getSelectionKey().attachment();
+            
             try {
+
+                if (attachment instanceof SelectionKeyAttachment) {
+                    ((SelectionKeyAttachment) 
attachment).setTimeout(SelectionKeyAttachment.UNLIMITED_TIMEOUT);
+                } else if (attachment instanceof Long) {
+                    
task.getSelectionKey().attach(SelectionKeyAttachment.UNLIMITED_TIMEOUT);
+                }
+
                 if (!asyncExecutor.execute())
                     return false;
             } catch (Exception e) {
                 throw new RuntimeException(e);
+            } finally {
+                if (attachment instanceof SelectionKeyAttachment) {
+                    ((SelectionKeyAttachment) 
attachment).setTimeout(System.currentTimeMillis());
+                } else if (attachment instanceof Long) {
+                    
task.getSelectionKey().attach(System.currentTimeMillis());
+                }
             }
             
             appReturnedAtMillis = System.currentTimeMillis();


diff -r 7c64e73e02d9 -r a6564a3e6908 
grizzly-sendfile-g19/src/main/java/com/igorminar/grizzlysendfile/KeepAliveHandler.java
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ 
b/grizzly-sendfile-g19/src/main/java/com/igorminar/grizzlysendfile/KeepAliveHandler.java
    Thu Jan 21 01:50:36 2010 -0800
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2008-2009 Igor Minar. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  
02110-1301, USA.
+ *
+ * The license can also be obtained at 
http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ */
+
+package com.igorminar.grizzlysendfile;
+
+import com.sun.grizzly.SelectorHandler;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+
+/**
+ *
+ * @author Igor Minar
+ */
+public class KeepAliveHandler implements ChannelClosureHandler {
+
+    private final SelectorHandler selectorHandler;
+    
+
+    KeepAliveHandler(SelectorHandler selectorHandler) {
+        this.selectorHandler = selectorHandler;
+    }
+
+
+
+    public boolean supportsKeepAlive() {
+        return true;
+    }
+
+
+    public void close(final SocketChannel channel) {
+        selectorHandler.register(channel, SelectionKey.OP_READ);
+    }
+}

diff -r 7c64e73e02d9 -r a6564a3e6908 
grizzly-sendfile-g19/src/main/java/com/igorminar/grizzlysendfile/SendfileFilter.java
--- 
a/grizzly-sendfile-g19/src/main/java/com/igorminar/grizzlysendfile/SendfileFilter.java
      Thu Jan 21 01:46:43 2010 -0800
+++ 
b/grizzly-sendfile-g19/src/main/java/com/igorminar/grizzlysendfile/SendfileFilter.java
      Thu Jan 21 01:50:36 2010 -0800
@@ -248,9 +248,11 @@
             }
             case 200: {
                 response.setHeader(ACCEPT_RANGES_HEADER, "bytes");
-                response.setHeader(CONNECTION_HEADER, "close"); //keep-alive 
doesn't work yet
                 response.setHeader(CONTENT_LENGTH_HEADER, 
Long.toString(download.getTotalSize()));
 
+                if (!task.isKeepAlive())
+                    response.setHeader(CONNECTION_HEADER, "close");
+
                 final long lastModified = dataSource.getLastModified();
                 response.setHeader(LAST_MODIFIED_HEADER,
                         FastHttpDateFormat.formatDate(lastModified, 
formats.getDefaultFormat()));
@@ -283,6 +285,12 @@
         }
 
         try {
+            response.flushHeaders();
+        } catch (IOException ex) {
+            logger.log(Level.INFO, "Failed to send headers", ex);
+        }
+
+        try {
             final SSLEngine sslEngine;
             Object attachedObject = task.getSelectionKey().attachment();
             if (attachedObject instanceof ThreadAttachment) {
@@ -292,18 +300,16 @@
                 sslEngine = null;
             }
 
+            final ChannelClosureHandler connCloseHandler = 
task.isKeepAlive() ?
+                    new KeepAliveHandler(task.getSelectorHandler()) : null;
+
             SendfileTask sendfileTask = new 
SendfileTask.Builder(selectorThread,
                                 (SocketChannel) 
(task.getSelectionKey().channel()),
                                 config, download, dataSource)
                                 .sslEngine(sslEngine)
+                                .channelClosureHandler(connCloseHandler)
                                 .build();
 
-            try {
-                response.flushHeaders();
-            } catch (IOException ex) {
-                logger.log(Level.INFO, "Failed to send headers", ex);
-            }
-
             // clean the response buffer
             // more info: 
https://grizzly.dev.java.net/issues/show_bug.cgi?id=336
             //response.discardUpstreamWrites();

diff -r 7c64e73e02d9 -r a6564a3e6908 
grizzly-sendfile-g19/src/test/java/com/igorminar/grizzlysendfile/Grizzly19KeepAliveTest.java
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ 
b/grizzly-sendfile-g19/src/test/java/com/igorminar/grizzlysendfile/Grizzly19KeepAliveTest.java
      Thu Jan 21 01:50:36 2010 -0800
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2008-2009 Igor Minar. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  
02110-1301, USA.
+ *
+ * The license can also be obtained at 
http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ */
+
+package com.igorminar.grizzlysendfile;
+
+import com.igorminar.grizzlysendfile.algorithm.SendfileAlgorithm;
+import com.sun.grizzly.SSLConfig;
+import com.sun.grizzly.arp.AsyncHandler;
+import com.sun.grizzly.arp.DefaultAsyncHandler;
+import com.sun.grizzly.http.SelectorThread;
+import com.sun.grizzly.ssl.SSLSelectorThread;
+import com.sun.grizzly.tcp.http11.GrizzlyAdapter;
+import com.sun.grizzly.tcp.http11.GrizzlyRequest;
+import com.sun.grizzly.tcp.http11.GrizzlyResponse;
+import com.sun.grizzly.util.net.jsse.JSSEImplementation;
+import java.io.IOException;
+import org.junit.After;
+
+/**
+ *
+ * @author Igor Minar
+ */
+public class Grizzly19KeepAliveTest extends AbstractKeepAliveTest {
+
+    private SelectorThread grizzlySelector;
+
+
+    public Grizzly19KeepAliveTest(SendfileAlgorithm algorithm, boolean 
enableSsl) {
+        super(algorithm, enableSsl);
+    }
+
+
+    @Override
+    public int getPort() {
+        return grizzlySelector.getPortLowLevel();
+    }
+
+
+    @After
+    @Override
+    public void tearDown() {
+        super.tearDown();
+        if (grizzlySelector != null) {
+            grizzlySelector.stopEndpoint();
+            grizzlySelector = null;
+        }
+    }
+
+
+    @Override
+    public SendfileSelectorThread createSelector(SendfileConfig config, 
boolean enableSsl) {
+        if (enableSsl) {
+            String keystorePath = getKeystorePath();
+            String keystorePass = getKeystorePass();
+
+            SSLConfig sslConfig = new SSLConfig();
+            sslConfig.setKeyStorePass(keystorePass);
+            sslConfig.setKeyStoreFile(keystorePath);
+
+            grizzlySelector = new SSLSelectorThread();
+            SSLSelectorThread sslSelector = (SSLSelectorThread) 
grizzlySelector;
+            sslSelector.setSSLConfig(sslConfig);
+            sslSelector.setSSLContext(sslConfig.createSSLContext());
+            try {
+                sslSelector.setSSLImplementation(new JSSEImplementation());
+            } catch (ClassNotFoundException ex) {
+                throw new RuntimeException(ex);
+            }
+        } else {
+            grizzlySelector = new SelectorThread();
+        }
+
+        SendfileFilter filter = new SendfileFilter(config);
+
+        AsyncHandler handler = new DefaultAsyncHandler();
+        handler.addAsyncFilter(filter);
+
+        grizzlySelector.setPort(0);
+        grizzlySelector.setAsyncHandler(handler);
+        grizzlySelector.setAdapter(new DummyAdapter());
+        grizzlySelector.setEnableAsyncExecution(true);
+
+        try {
+            grizzlySelector.listen();
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        } catch (InstantiationException ex) {
+            throw new RuntimeException(ex);
+        }
+
+        return filter.getSelectorThread();
+    }
+
+
+    class DummyAdapter extends GrizzlyAdapter {
+
+        @Override
+        public void service(GrizzlyRequest request, GrizzlyResponse 
response) {
+            try {
+                response.addHeader(SendfileFilter.X_SENDFILE_HEADER, 
getFile().getCanonicalPath());
+            } catch (IOException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+    }
+}

diff -r 7c64e73e02d9 -r a6564a3e6908 
sendfile-core/src/main/java/com/igorminar/grizzlysendfile/ChannelClosureHandler.java
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ 
b/sendfile-core/src/main/java/com/igorminar/grizzlysendfile/ChannelClosureHandler.java
      Thu Jan 21 01:50:36 2010 -0800
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2008-2009 Igor Minar. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  
02110-1301, USA.
+ *
+ * The license can also be obtained at 
http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ */
+
+package com.igorminar.grizzlysendfile;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.channels.SocketChannel;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Handles closure of a SocketChannel. Handy for implementing keep-alive 
support
+ * during integration with an http front end.
+ *
+ * @author Igor Minar
+ */
+public interface ChannelClosureHandler {
+
+
+    /**
+     * @return true if the handler supports persistent connections (HTTP 
keep-alive)
+     */
+    boolean supportsKeepAlive();
+
+
+    /**
+     * "Closes" a SocketChannel. If handler supports HTTP keep-alive, the 
channel
+     * will not be actually closed, but instead it will be registered with an
+     * appropriate selector so that it can be reused for future requests.
+     *
+     * @param channel to close
+     */
+    void close(SocketChannel channel);
+
+    
+
+    /**
+     * Default implementatation of the handler, which does close the channel 
by
+     * calling #close() on the channel's socket as well as on the channel 
itself.
+     */
+    static class DefaultChannelClosureHandler implements 
ChannelClosureHandler {
+
+        private static final Logger logger = 
Logger.getLogger(DefaultChannelClosureHandler.class.getName());
+
+
+        /** {@inheritDoc} */
+        public boolean supportsKeepAlive() {
+            return false;
+        }
+
+
+        /** {@inheritDoc} */
+        public void close(SocketChannel channel) {
+            Socket socket = channel.socket();
+
+            try {
+                socket.close();
+            } catch (IOException ex) {
+                logger.log(Level.INFO, "Problems while closing socket", ex);
+            }
+
+            try {
+                channel.close();
+            } catch (IOException ex) {
+                logger.log(Level.INFO, "Problems while closing 
SocketChannel", ex);
+            }
+        }
+    }
+}

diff -r 7c64e73e02d9 -r a6564a3e6908 
sendfile-core/src/main/java/com/igorminar/grizzlysendfile/Download.java
--- a/sendfile-core/src/main/java/com/igorminar/grizzlysendfile/Download.java 
  Thu Jan 21 01:46:43 2010 -0800
+++ b/sendfile-core/src/main/java/com/igorminar/grizzlysendfile/Download.java 
  Thu Jan 21 01:50:36 2010 -0800
@@ -33,7 +33,11 @@
 
     private static final String HEAD_METHOD = "HEAD";
 
-    public enum Status { INITIALIZED, QUEUED, WAITING, SENDING, FINISHED };
+    public enum Status { INITIALIZED,  //when a download object is 
constructed
+                         QUEUED,       //when it is put into registration 
queue
+                         WAITING,      //when a download is registered with 
the ExecutionServices
+                         SENDING,      //when a download is being streamed
+                         FINISHED };   //when a download has completed
 
     private long totalBytesRead;
     private long totalBytesSent;

diff -r 7c64e73e02d9 -r a6564a3e6908 
sendfile-core/src/main/java/com/igorminar/grizzlysendfile/SendfileSelectorThread.java
--- 
a/sendfile-core/src/main/java/com/igorminar/grizzlysendfile/SendfileSelectorThread.java
     Thu Jan 21 01:46:43 2010 -0800
+++ 
b/sendfile-core/src/main/java/com/igorminar/grizzlysendfile/SendfileSelectorThread.java
     Thu Jan 21 01:50:36 2010 -0800
@@ -352,8 +352,12 @@
             
             SendfileTask task = it.next();
             it.remove();
-            task.registerWithSelector(selector);
-            monitoring.downloadStarted(task.getDownload());
+            if (task.hasCanceledSelectionKey(selector)) {
+                pendingReregistrationWithCanceledKeyQueue.add(task);
+            } else {
+                task.registerWithSelector(selector);
+            }
+            monitoring.downloadStarted(task.getDownload()); //TODO not yet 
if pending
         }
     }
     

diff -r 7c64e73e02d9 -r a6564a3e6908 
sendfile-core/src/main/java/com/igorminar/grizzlysendfile/SendfileTask.java
--- 
a/sendfile-core/src/main/java/com/igorminar/grizzlysendfile/SendfileTask.java 
      Thu Jan 21 01:46:43 2010 -0800
+++ 
b/sendfile-core/src/main/java/com/igorminar/grizzlysendfile/SendfileTask.java 
      Thu Jan 21 01:50:36 2010 -0800
@@ -20,11 +20,11 @@
 
 package com.igorminar.grizzlysendfile;
 
+import 
com.igorminar.grizzlysendfile.ChannelClosureHandler.DefaultChannelClosureHandler;
 import com.igorminar.grizzlysendfile.algorithm.SendfileAlgorithm;
 import com.igorminar.grizzlysendfile.plugin.SendfilePlugin;
 import com.igorminar.grizzlysendfile.plugin.SslBufferProcessor;
 import java.io.IOException;
-import java.net.Socket;
 import java.net.SocketException;
 import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
@@ -72,6 +72,14 @@
 
 
     /**
+     * @param selector a selector
+     * @return true if there is a SelectionKey for the given selector and 
task's
+     *          SocketChannel and this key is #invalid()
+     */
+    boolean hasCanceledSelectionKey(Selector selector);
+
+
+    /**
      * Closes or clears all the resources associated with this task (buffer,
      * dataSource, socketChannel, etc).
      *
@@ -93,6 +101,8 @@
         
         private static final Logger logger = 
Logger.getLogger(SendfileTask.class.getName());
         private static final ByteBuffer NULL_BUFFER = ByteBuffer.allocate(0);
+        private static final ChannelClosureHandler defaultConnCloseHandler =
+                                            new 
DefaultChannelClosureHandler();
         
 
 
@@ -110,6 +120,7 @@
         private final ProcessorPipeline processorPipeline;
         private boolean hasMoreToSend;
         private SelectionKey selectionKey;
+        private final ChannelClosureHandler connCloseHandler;
 
 
 
@@ -117,7 +128,8 @@
 
         DefaultSendfileTask(SendfileSelectorThread selectorThread, 
SocketChannel socketChannel,
                 SendfileConfig config, Download download, DataSourceReader 
dataSourceReader,
-                ProcessorPipeline processorPipeline, ByteBuffer buffer) {
+                ProcessorPipeline processorPipeline, ByteBuffer buffer,
+                ChannelClosureHandler connCloseHandler) {
 
             this.selectorThread = selectorThread;
             this.socketChannel = socketChannel;
@@ -129,6 +141,8 @@
             this.rawBuffer = buffer;
             this.bufferSize = buffer.capacity();
             this.processedBuffer = NULL_BUFFER; //init just for 
thByteBuffere inital iteration
+            this.connCloseHandler = connCloseHandler == null ?
+                                        defaultConnCloseHandler : 
connCloseHandler;
         }
 
         public void run() {
@@ -256,6 +270,13 @@
 
 
         /** {@inheritDoc} */
+        public boolean hasCanceledSelectionKey(Selector selector) {
+            SelectionKey key = socketChannel.keyFor(selector);
+            return key != null && !key.isValid();
+        }
+
+
+        /** {@inheritDoc} */
         public void close(int downloadResult) {
             synchronized (this) {
                 if (download.isFinished())
@@ -284,28 +305,27 @@
 
             dataSourceReader.close();
 
-            if (socketChannel != null) {
-
-                Socket socket = socketChannel.socket();
-                
-                try {
-                    socket.close();
-                } catch (IOException ex) {
-                    logger.log(Level.INFO, "Problems while closing socket", 
ex);
-                }
-
-                try {
-                    socketChannel.close();
-                } catch (IOException ex) {
-                    logger.log(Level.INFO, "Problems while closing 
SocketChannel", ex);
-                }
-            }
-
             if (selectionKey != null) {
                 selectionKey.cancel();
                 selectionKey.attach(null);
             }
 
+            if (socketChannel != null) {
+                try {
+                    socketChannel.configureBlocking(false);
+                } catch (IOException ex) {
+                    logger.log(Level.WARNING, "Failed to switch channel into 
" +
+                            "nonblocking mode before closing it", ex);
+                } finally {
+                    try {
+                        connCloseHandler.close(socketChannel);
+                    } catch (Exception ex) {
+                        logger.log(Level.WARNING, "Failed to close the 
connection with " +
+                                connCloseHandler.getClass() + ", using the 
default handler", ex);
+                        defaultConnCloseHandler.close(socketChannel);
+                    }
+                }
+            }
 
             
selectorThread.getSendfileMonitoring().downloadFinished(download);
 
@@ -369,6 +389,7 @@
         private SSLEngine sslEngine;
         private ByteBuffer rawBuffer;
         private ProcessorPipeline pipeline;
+        private ChannelClosureHandler channelClosureHandler;
 
 
         Builder(SendfileSelectorThread selectorThread, SocketChannel 
socketChannel,
@@ -387,6 +408,12 @@
         }
 
 
+        Builder channelClosureHandler(ChannelClosureHandler handler) {
+            this.channelClosureHandler = handler;
+            return this;
+        }
+
+
         SendfileTask build() {
 
             //Determine the raw buffer size
@@ -428,7 +455,8 @@
             DataSourceReader reader = 
dataSource.getReader(download.getRange());
             
             return new DefaultSendfileTask(selectorThread, socketChannel, 
config,
-                                    download, reader, pipeline, rawBuffer);
+                                    download, reader, pipeline, rawBuffer,
+                                    channelClosureHandler);
         }
     }
 }

diff -r 7c64e73e02d9 -r a6564a3e6908 
sendfile-core/src/test/java/com/igorminar/grizzlysendfile/DefaultChannelClosureHandlerTest.java
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ 
b/sendfile-core/src/test/java/com/igorminar/grizzlysendfile/DefaultChannelClosureHandlerTest.java
   Thu Jan 21 01:50:36 2010 -0800
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2008-2009 Igor Minar. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  
02110-1301, USA.
+ *
+ * The license can also be obtained at 
http://www.gnu.org/licenses/gpl-2.0.html
+ */
+
+package com.igorminar.grizzlysendfile;
+
+import 
com.igorminar.grizzlysendfile.ChannelClosureHandler.DefaultChannelClosureHandler;
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.channels.SocketChannel;
+import org.junit.Ignore;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
+import static org.mockito.Mockito.*;
+
+/**
+ *
+ * @author Igor Minar
+ */
+public class DefaultChannelClosureHandlerTest {
+
+    @Test
+    public void testSupportsKeepAlive() {
+        ChannelClosureHandler handler = new DefaultChannelClosureHandler();
+
+        assertThat("handler doesn't support keep-alive",
+                handler.supportsKeepAlive(), is(false));
+    }
+
+
+    @Test
+    @Ignore(value="this is currently untestable because of final methods in 
" +
+            "SocketChannel which can't be mocked")
+    public void testClose() throws IOException {
+        ChannelClosureHandler handler = new DefaultChannelClosureHandler();
+        SocketChannel channel = mock(SocketChannel.class);
+        Socket socket = mock(Socket.class);
+        //doThrow(new RuntimeException()).when(channel).close();
+        when(channel.socket()).thenReturn(socket);
+
+        handler.close(channel);
+
+        verify(channel).close();
+        verify(socket).close();
+    }
+}

diff -r 7c64e73e02d9 -r a6564a3e6908 
sendfile-core/src/test/java/com/igorminar/grizzlysendfile/SendfileSelectorThreadTest.java
--- 
a/sendfile-core/src/test/java/com/igorminar/grizzlysendfile/SendfileSelectorThreadTest.java
 Thu Jan 21 01:46:43 2010 -0800
+++ 
b/sendfile-core/src/test/java/com/igorminar/grizzlysendfile/SendfileSelectorThreadTest.java
 Thu Jan 21 01:50:36 2010 -0800
@@ -240,6 +240,11 @@
             return false;
         }
 
+        
+        public boolean hasCanceledSelectionKey(Selector selector) {
+            return false;
+        }
+
 
         public void close(int downloadResult) {
             download.finish(downloadResult);

diff -r 7c64e73e02d9 -r a6564a3e6908 
sendfile-test/src/main/java/com/igorminar/grizzlysendfile/AbstractIntegrationTest.java
--- 
a/sendfile-test/src/main/java/com/igorminar/grizzlysendfile/AbstractIntegrationTest.java
    Thu Jan 21 01:46:43 2010 -0800
+++ 
b/sendfile-test/src/main/java/com/igorminar/grizzlysendfile/AbstractIntegrationTest.java
    Thu Jan 21 01:50:36 2010 -0800
@@ -29,22 +29,14 @@
 import com.igorminar.grizzlysendfile.jmx.SendfileStats;
 import java.io.BufferedInputStream;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.net.HttpURLConnection;
-import java.net.URL;
-import java.security.KeyStore;
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManagerFactory;
 import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -59,16 +51,7 @@
  * @author Igor Minar
  */
 @RunWith(Parameterized.class)
-public abstract class AbstractIntegrationTest {
-    private static final String KEYSTORE_FILE = "localhost.jks";
-    private static final String KEYSTORE_PASS = "changeit";
-
-    private SendfileAlgorithm algorithm;
-    private boolean enableSsl;
-
-    private File tmpFile;
-    private SendfileSelectorThread selector;
-
+public abstract class AbstractIntegrationTest extends AbstractSendfileTest {
 
     @Parameters
     public static List<Object[]> parameters() {
@@ -86,20 +69,14 @@
         this.enableSsl = enableSsl;
     }
 
-    public abstract int getPort();
-
-    public abstract SendfileSelectorThread createSelector(SendfileConfig 
config, boolean enableSsl);
-
 
     @After
+    @Override
     public void tearDown() {
-        if (tmpFile != null)
-            tmpFile.delete();
-        
-        if (selector != null)
-            selector.stop();
+        super.tearDown();
     }
 
+
     
     @Test
     public void testSuccessfulDownload() throws IOException {
@@ -691,131 +668,4 @@
             throw new RuntimeException(ex);
         }
     }
-
-
-
-    File getFile() {
-        return tmpFile;
-    }
-
-
-    String getKeystorePath() {
-        return 
getClass().getClassLoader().getResource(KEYSTORE_FILE).getPath();
-    }
-
-
-    String getKeystorePass() {
-        return KEYSTORE_PASS;
-    }
-
-    
-    /**
-     * Creates http selector using the given config
-     * 
-     * @param config
-     * @return
-     */
-    public SendfileSelectorThread createSelector(SendfileConfig config) {
-        return createSelector(config, false);
-    }
-
-
-    private void writeRandomStuff(OutputStream os, long length) {
-        try {
-            long interations = length / 1000;
-            int reminder = (int)(length % 1000);
-            for (long i = 0; i < interations; i++) {
-                os.write(generateRandomContent(1000));
-            }
-            os.write(generateRandomContent(reminder));
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
-
-    private byte[] generateRandomContent(int length) {
-        byte[] content = new byte[length];
-        int generated = 0;
-        while (generated < length) {
-            byte[] c = Double.toHexString(Math.random()).getBytes();
-            System.arraycopy(c, 0, content, generated, Math.min(c.length, 
content.length - generated));
-            generated += c.length;
-        }
-
-        return content;
-    }
-
-
-    private Object[] generateRandomFile(int generatedContentLength) {
-        try {
-            tmpFile = File.createTempFile("grizzly-sendfile", null);
-            tmpFile.deleteOnExit();
-
-            byte[] generatedContent = 
generateRandomContent(generatedContentLength);
-
-            FileOutputStream fos = new FileOutputStream(tmpFile);
-            fos.write(generatedContent);
-            fos.close();
-
-            return new Object[] {generatedContent, tmpFile};
-            
-        } catch (Exception ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
-
-    private HttpURLConnection createConnection(int port, String uriPath, 
boolean enableSsl) {
-
-        final HttpURLConnection conn;
-
-        try {
-            if (enableSsl) {
-
-                KeyStore keyStore = 
KeyStore.getInstance(KeyStore.getDefaultType());
-                keyStore.load(new FileInputStream(new 
File(getKeystorePath())), getKeystorePass().toCharArray());
-
-                TrustManagerFactory tmf = 
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
-                tmf.init(keyStore);
-                SSLContext ctx = SSLContext.getInstance("TLS");
-                ctx.init(null, tmf.getTrustManagers(), null);
-                SSLSocketFactory sslFactory = ctx.getSocketFactory();
-
-                URL url = new URL("https://localhost:" + port + uriPath);
-                HttpsURLConnection sslConn = (HttpsURLConnection) 
url.openConnection();
-                sslConn.setSSLSocketFactory(sslFactory);
-                conn = sslConn;
-
-            } else {
-
-                URL url = new URL("http://localhost:" + getPort() + uriPath);
-                conn = (HttpURLConnection) url.openConnection();
-
-            }
-
-            conn.setRequestMethod("GET");
-            conn.setUseCaches(false);
-            
-        } catch (Exception ex) {
-            throw new RuntimeException(ex);
-        }
-
-        return conn;
-    }
-
-
-    private HttpURLConnection createConnection(int port, boolean enableSsl) {
-        return createConnection(port, "/test-file", enableSsl);
-    }
-
-
-    private HttpURLConnection createConnection(int port, String uriPath) {
-        return createConnection(port, uriPath, false);
-    }
-
-
-    private HttpURLConnection createConnection(int port) {
-        return createConnection(port, "/test-file", false);
-    }
 }

diff -r 7c64e73e02d9 -r a6564a3e6908 
sendfile-test/src/main/java/com/igorminar/grizzlysendfile/AbstractKeepAliveTest.java
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ 
b/sendfile-test/src/main/java/com/igorminar/grizzlysendfile/AbstractKeepAliveTest.java
      Thu Jan 21 01:50:36 2010 -0800
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2008-2009 Igor Minar. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  
02110-1301, USA.
+ *
+ * The license can also be obtained at 
http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ */
+
+package com.igorminar.grizzlysendfile;
+
+import com.igorminar.grizzlysendfile.algorithm.EqualBlockingAlgorithm;
+import com.igorminar.grizzlysendfile.algorithm.EqualNonBlockingAlgorithm;
+import com.igorminar.grizzlysendfile.algorithm.SendfileAlgorithm;
+import com.igorminar.grizzlysendfile.algorithm.SimpleBlockingAlgorithm;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
+
+/**
+ *
+ * @author Igor Minar
+ */
+@RunWith(Parameterized.class)
+public abstract class AbstractKeepAliveTest extends AbstractSendfileTest {
+
+    @Parameters
+    public static List<Object[]> parameters() {
+        return Arrays.asList(new Object[][] { { new 
EqualBlockingAlgorithm(), false },
+                                              //{ new 
EqualBlockingAlgorithm(), true }, //TODO
+                                              { new 
EqualNonBlockingAlgorithm(), false },
+                                              //{ new 
EqualNonBlockingAlgorithm(), true }, //TODO
+                                              { new 
SimpleBlockingAlgorithm(), false },
+                                              //{ new 
SimpleBlockingAlgorithm(), true }, //TODO
+                                            });
+    }
+
+
+    public AbstractKeepAliveTest(SendfileAlgorithm algorithm, boolean 
enableSsl) {
+        this.algorithm = algorithm;
+        this.enableSsl = enableSsl;
+    }
+
+
+    public abstract int getPort();
+
+
+    public abstract SendfileSelectorThread createSelector(SendfileConfig 
config, boolean enableSsl);
+
+
+    @After
+    @Override
+    public void tearDown() {
+        super.tearDown();
+    }
+
+
+    @Test
+    public void keepAliveTest() throws IOException {
+        String tmpDir = new 
File(System.getProperty("java.io.tmpdir")).getCanonicalPath();
+        SendfileConfig config = new SendfileConfig.Builder(tmpDir + "/*")
+                                        .algorithm(algorithm)
+                                        .build();
+        
+        selector = createSelector(config, enableSsl);
+        Socket socket = createSocket(getPort(), enableSsl);
+
+        int generatedContentLength = 100;
+        Object[] result = generateRandomFile(generatedContentLength);
+        byte[] generatedContent = (byte[]) result[0];
+        tmpFile = (File) result[1];
+
+        PrintWriter w = new PrintWriter(socket.getOutputStream());
+        InputStream is = socket.getInputStream();
+
+
+        for (int i=0; i<5; i++) {
+            byte[] content = requestFile(w, is, generatedContentLength);
+
+            assertThat("content matches the expected data",
+                        content, equalTo(generatedContent));
+        }
+    }
+
+
+    private byte[] requestFile(PrintWriter w, InputStream is, int length) 
throws IOException {
+        w.println("GET /foo HTTP/1.1");
+        w.println("Host: localhost");
+        w.println();
+        w.flush();
+
+        InputStreamReader r = new InputStreamReader(is);
+        char[] c = new char[1];
+        StringBuilder b = new StringBuilder(length);
+        int len = 0;
+
+        while (true) {
+
+            while (true) {
+                int read = r.read(c);
+                if (c[0] == '\r') {
+                    r.read(c);
+                    break;
+                } else if (read == -1) {
+                    throw new RuntimeException("EOF Reached");
+                }
+                    
+                b.append(c);
+            }
+
+            String header = b.toString();
+            
+            if (header.startsWith("Content-Length: "))
+                len = Integer.valueOf(b.toString().split(" ")[1]);
+
+            if (header.equals(""))
+                break;
+
+            b.delete(0, b.length());
+        }
+
+        byte[] content = new byte[len];
+        is.read(content);
+        
+        assertThat("Conte-Length matches the expected value",
+                len, is(length));
+
+        assertThat("there is no more data to be read",
+                is.available(), is(0));
+
+
+        return content;
+    }
+}
\ No newline at end of file

diff -r 7c64e73e02d9 -r a6564a3e6908 
sendfile-test/src/main/java/com/igorminar/grizzlysendfile/AbstractSendfileTest.java
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ 
b/sendfile-test/src/main/java/com/igorminar/grizzlysendfile/AbstractSendfileTest.java
       Thu Jan 21 01:50:36 2010 -0800
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2008-2009 Igor Minar. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  
02110-1301, USA.
+ *
+ * The license can also be obtained at 
http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ */
+
+package com.igorminar.grizzlysendfile;
+
+import com.igorminar.grizzlysendfile.algorithm.SendfileAlgorithm;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.Socket;
+import java.net.URL;
+import java.security.KeyStore;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManagerFactory;
+import org.junit.After;
+
+/**
+ *
+ * @author Igor Minar
+ */
+public abstract class AbstractSendfileTest {
+
+    protected static final String KEYSTORE_FILE = "localhost.jks";
+    protected static final String KEYSTORE_PASS = "changeit";
+
+    protected SendfileAlgorithm algorithm;
+    protected boolean enableSsl;
+
+    protected File tmpFile;
+    protected SendfileSelectorThread selector;
+
+
+    public abstract int getPort();
+
+    public abstract SendfileSelectorThread createSelector(SendfileConfig 
config, boolean enableSsl);
+
+
+    @After
+    public void tearDown() {
+        if (tmpFile != null)
+            tmpFile.delete();
+
+        if (selector != null)
+            selector.stop();
+    }
+
+
+    File getFile() {
+        return tmpFile;
+    }
+
+
+    String getKeystorePath() {
+        return 
getClass().getClassLoader().getResource(KEYSTORE_FILE).getPath();
+    }
+
+
+    String getKeystorePass() {
+        return KEYSTORE_PASS;
+    }
+
+
+    /**
+     * Creates http selector using the given config
+     *
+     * @param config
+     * @return
+     */
+    public SendfileSelectorThread createSelector(SendfileConfig config) {
+        return createSelector(config, false);
+    }
+
+
+    protected void writeRandomStuff(OutputStream os, long length) {
+        try {
+            long interations = length / 1000;
+            int reminder = (int)(length % 1000);
+            for (long i = 0; i < interations; i++) {
+                os.write(generateRandomContent(1000));
+            }
+            os.write(generateRandomContent(reminder));
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+
+    protected byte[] generateRandomContent(int length) {
+        byte[] content = new byte[length];
+        int generated = 0;
+        while (generated < length) {
+            byte[] c = Double.toHexString(Math.random()).getBytes();
+            System.arraycopy(c, 0, content, generated, Math.min(c.length, 
content.length - generated));
+            generated += c.length;
+        }
+
+        return content;
+    }
+
+
+    protected Object[] generateRandomFile(int generatedContentLength) {
+        try {
+            tmpFile = File.createTempFile("grizzly-sendfile", null);
+            tmpFile.deleteOnExit();
+
+            byte[] generatedContent = 
generateRandomContent(generatedContentLength);
+
+            FileOutputStream fos = new FileOutputStream(tmpFile);
+            fos.write(generatedContent);
+            fos.close();
+
+            return new Object[] {generatedContent, tmpFile};
+
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+
+    protected HttpURLConnection createConnection(int port, String uriPath, 
boolean enableSsl) {
+
+        final HttpURLConnection conn;
+
+        try {
+            if (enableSsl) {
+
+                KeyStore keyStore = 
KeyStore.getInstance(KeyStore.getDefaultType());
+                keyStore.load(new FileInputStream(new 
File(getKeystorePath())), getKeystorePass().toCharArray());
+
+                TrustManagerFactory tmf = 
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+                tmf.init(keyStore);
+                SSLContext ctx = SSLContext.getInstance("TLS");
+                ctx.init(null, tmf.getTrustManagers(), null);
+                SSLSocketFactory sslFactory = ctx.getSocketFactory();
+
+                URL url = new URL("https://localhost:" + port + uriPath);
+                HttpsURLConnection sslConn = (HttpsURLConnection) 
url.openConnection();
+                sslConn.setSSLSocketFactory(sslFactory);
+                conn = sslConn;
+
+            } else {
+
+                URL url = new URL("http://localhost:" + getPort() + uriPath);
+                conn = (HttpURLConnection) url.openConnection();
+
+            }
+
+            conn.setRequestMethod("GET");
+            conn.setUseCaches(false);
+
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+
+        return conn;
+    }
+
+
+    protected HttpURLConnection createConnection(int port, boolean 
enableSsl) {
+        return createConnection(port, "/test-file", enableSsl);
+    }
+
+
+    protected HttpURLConnection createConnection(int port, String uriPath) {
+        return createConnection(port, uriPath, false);
+    }
+
+
+    protected HttpURLConnection createConnection(int port) {
+        return createConnection(port, "/test-file", false);
+    }
+
+
+    protected Socket createSocket(int port, boolean enableSsl) {
+        try {
+
+            if (!enableSsl) {
+                return new Socket("localhost", port);
+            } {
+                KeyStore keyStore = 
KeyStore.getInstance(KeyStore.getDefaultType());
+                keyStore.load(new FileInputStream(new 
File(getKeystorePath())), getKeystorePass().toCharArray());
+
+                TrustManagerFactory tmf = 
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+                tmf.init(keyStore);
+                SSLContext ctx = SSLContext.getInstance("TLS");
+                ctx.init(null, tmf.getTrustManagers(), null);
+                SSLSocketFactory sslFactory = ctx.getSocketFactory();
+
+                return sslFactory.createSocket("localhost", port);
+            }
+
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+}






[grizzly-sendfile~main:283] adding support for persistent connections (HTTP keep-alive)

igor_minar 01/25/2010
  • Mysql
  • Glassfish
  • Jruby
  • Rails
  • Nblogo
Terms of Use; Privacy Policy;
© 2010, Oracle Corporation and/or its affiliates
(revision 20120127.ac94057)
 
 
Close
loading
Please Confirm
Close