[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 |





