[Jython-checkins] jython (2.5): #1754: Fix modjy does not provide appropriate wsgi.input file-like object.
frank.wierzbicki
jython-checkins at python.org
Thu Jul 5 18:48:11 CEST 2012
http://hg.python.org/jython/rev/bc783e270abc
changeset: 6780:bc783e270abc
branch: 2.5
parent: 6711:0ea5676c29a8
user: Frank Wierzbicki <fwierzbicki at gmail.com>
date: Thu Jul 05 09:24:49 2012 -0700
summary:
#1754: Fix modjy does not provide appropriate wsgi.input file-like object.
Thanks for initial patch Philip Jenvey!
files:
Lib/test/test_io_jy.py | 66 ++++++++++++
NEWS | 4 +
src/org/python/core/io/StreamIO.java | 85 +++++++++++++++-
3 files changed, 154 insertions(+), 1 deletions(-)
diff --git a/Lib/test/test_io_jy.py b/Lib/test/test_io_jy.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test_io_jy.py
@@ -0,0 +1,66 @@
+"""Misc io tests.
+
+Made for Jython.
+"""
+import unittest
+
+from org.python.core.util import FileUtil
+from org.python.core.io import StreamIO
+
+from java.io import InputStream
+from java.nio import ByteBuffer;
+
+class InfiniteInputStream(InputStream):
+
+ def read(self, *args):
+ if len(args) == 0:
+ return ord('x')
+ elif len(args) == 1:
+ return InputStream.read(self, args[0])
+ else:
+ return self.read_buffer(*args)
+
+ def read_buffer(self, buf, off, length):
+ if length > 0:
+ buf[off] = ord('x')
+ return 1
+ return 0
+
+
+class IoTestCase(unittest.TestCase):
+ """
+ Jython was failing to read all available content when an InputStream
+ returns early. Java's InputStream.read() is allowed to return less than the
+ requested # of bytes under non-exceptional/EOF conditions, whereas
+ (for example) wsgi.input requires the file.read() method to block until the
+ requested # of bytes are available (except for exceptional/EOF conditions).
+
+ See http://bugs.jython.org/issue1754 for more discussion.
+ """
+ def test_infinite_input(self):
+ iis = InfiniteInputStream()
+ f = FileUtil.wrap(iis, 'rb')
+ size = 10000
+ self.assertEqual(len(f.read(size)), size)
+ self.assertEqual(len(f.read(size)), size)
+ self.assertEqual(len(f.read(size)), size)
+
+ def test_buffer_no_array(self):
+ """
+ Directly tests StreamIO with and without a backing array and an
+ InputStream that returns early.
+ """
+ size = 10000
+ without_array = ByteBuffer.allocateDirect(size)
+ self.assertFalse(without_array.hasArray())
+ with_array = ByteBuffer.allocate(size)
+ self.assertTrue(with_array.hasArray())
+ bbs = [with_array, without_array]
+ for bb in bbs:
+ iis = InfiniteInputStream()
+ io = StreamIO(iis, True)
+ self.assertEqual(io.readinto(bb), size)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/NEWS b/NEWS
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,10 @@
Jython 2.7a1
Bugs Fixed
+Jython 2.5.3b3
+ Bugs Fixed
+ - [ 1754 ] modjy does not provide appropriate wsgi.input file-like object
+
Jython 2.5.3b2
Bugs Fixed
- [ 1908 ] Patch for 'Unmapped exception: java.net.NoRouteToHostException'
diff --git a/src/org/python/core/io/StreamIO.java b/src/org/python/core/io/StreamIO.java
--- a/src/org/python/core/io/StreamIO.java
+++ b/src/org/python/core/io/StreamIO.java
@@ -16,6 +16,8 @@
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
+import java.nio.channels.spi.AbstractInterruptibleChannel;
+
import org.python.core.Py;
import org.python.modules.posix.PosixModule;
@@ -95,7 +97,7 @@
* close() (defaults to True)
*/
public StreamIO(InputStream inputStream, boolean closefd) {
- this(Channels.newChannel(inputStream), closefd);
+ this(newChannel(inputStream), closefd);
this.inputStream = inputStream;
}
@@ -107,6 +109,8 @@
* close() (defaults to True)
*/
public StreamIO(OutputStream outputStream, boolean closefd) {
+ //XXX: It turns out we needed to write our own channel for
+ // InputStreams. Do we need to do the same for OutputStreams?
this(Channels.newChannel(outputStream), closefd);
this.outputStream = outputStream;
}
@@ -270,4 +274,83 @@
public Channel getChannel() {
return readable() ? readChannel : writeChannel;
}
+
+ private static ReadableByteChannel newChannel(InputStream in) {
+ return new InternalReadableByteChannel(in);
+ }
+
+
+ /*
+ * AbstractInterruptibleChannel is used for its end() and begin() implementations
+ * but this Channel is not really interruptible.
+ */
+ private static class InternalReadableByteChannel
+ extends AbstractInterruptibleChannel
+ implements ReadableByteChannel {
+
+ private InputStream in;
+ private boolean open = true;
+
+ InternalReadableByteChannel(InputStream in) {
+ this.in = in;
+ }
+
+ public int read(ByteBuffer dst) throws IOException {
+ final int CHUNK = 8192;
+
+ int len = dst.remaining();
+ int totalRead = 0;
+ int bytesRead = 0;
+ if (dst.hasArray()) {
+ while (totalRead < len) {
+ // array() can throw RuntimeExceptions but really shouldn't when
+ // hasArray() is true
+ try {
+ begin();
+ bytesRead = in.read(dst.array(), dst.arrayOffset(), dst.remaining());
+ } finally {
+ end(bytesRead > 0);
+ }
+ if (bytesRead < 0) {
+ break;
+ } else {
+ dst.position(dst.position() + bytesRead);
+ totalRead += bytesRead;
+ }
+ }
+ } else {
+ byte buf[] = new byte[0];
+ while (totalRead < len) {
+ int bytesToRead = Math.min((len - totalRead), CHUNK);
+ if (buf.length < bytesToRead) {
+ buf = new byte[bytesToRead];
+ }
+ try {
+ begin();
+ bytesRead = in.read(buf, 0, bytesToRead);
+ } finally {
+ end(bytesRead > 0);
+ }
+ if (bytesRead < 0) {
+ break;
+ } else {
+ totalRead += bytesRead;
+ }
+ dst.put(buf, 0, bytesRead);
+ }
+ }
+ if ((bytesRead < 0) && (totalRead == 0)) {
+ return -1;
+ }
+ return totalRead;
+ }
+
+ protected void implCloseChannel() throws IOException {
+ if (open) {
+ in.close();
+ open = false;
+ }
+ }
+ }
+
}
--
Repository URL: http://hg.python.org/jython
More information about the Jython-checkins
mailing list