From 524f034bacf44d93087c32023f1f16383b4ff5f5 Mon Sep 17 00:00:00 2001 From: keinhaar Date: Sun, 20 Dec 2015 12:42:36 +0100 Subject: [PATCH] Added support for HTTP URL connections, and fixed SocketInputStream and BufferedInputStream. Did some cleanup as proposed by the main developers. - Bigger HTTP Header Buffer - Exception if Header is anyway exceeded. - Linebreaks on HTTP Request fixed to standard. - Only stop header reading on \r\n\r\n and no longer on \n\n\n\n - Simplyfied the code to stop if buffer could not be filled. - Handle special case if buffer has length 0, like specified in the Java API - Socket will no longer fill the buffer completely --- classpath/avian/http/Handler.java | 114 +++++++++++++++++++-- classpath/java/io/BufferedInputStream.java | 29 +++--- classpath/java/net/Socket.java | 16 ++- test/BufferedInputStreamTest.java | 80 +++++++++++++++ 4 files changed, 205 insertions(+), 34 deletions(-) create mode 100644 test/BufferedInputStreamTest.java diff --git a/classpath/avian/http/Handler.java b/classpath/avian/http/Handler.java index e022b96940..28f36d4194 100644 --- a/classpath/avian/http/Handler.java +++ b/classpath/avian/http/Handler.java @@ -10,17 +10,111 @@ package avian.http; -import java.net.URL; -import java.net.URLStreamHandler; -import java.net.URLConnection; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.FileNotFoundException; -import java.io.File; -import java.io.FileInputStream; import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.Socket; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.util.HashMap; +import java.util.Map; -public class Handler extends URLStreamHandler { - protected URLConnection openConnection(URL url) { - throw new UnsupportedOperationException(); - } +public class Handler extends URLStreamHandler +{ + public URLConnection openConnection(URL url) throws IOException + { + return new HttpURLConnection(url); + } + + class HttpURLConnection extends URLConnection + { + Socket socket; + private BufferedWriter writer; + private InputStream bin; + private Map header = new HashMap(); + private int status; + + protected HttpURLConnection(URL url) + { + super(url); + } + + @Override + public void connect() throws IOException + { + if(socket == null) + { + URLConnection con = null; + String host = url.getHost(); + int port =url.getPort(); + if(port < 0) port = 80; + socket = new Socket(host, port); + OutputStream out = socket.getOutputStream(); + writer = new BufferedWriter(new OutputStreamWriter(out)); + writer.write("GET " + url.getPath() + " HTTP/1.1"); + writer.write("\r\nHost: " + host); + writer.write("\r\n\r\n"); + writer.flush(); + bin = new BufferedInputStream(socket.getInputStream()); + readHeader(); +// System.out.println("Status: " + status); +// System.out.println("Headers: " + header); + } + } + + private void readHeader() throws IOException + { + byte[] buf = new byte[8192]; + int b = 0; + int index = 0; + while(b >= 0) + { + if(index >= 4 && buf[index-4] == '\r' && buf[index-3] == '\n' && buf[index-2] == '\r' && buf[index-1] == '\n') + { + break; + } + b = bin.read(); + buf[index] = (byte) b; + index++; + if(index >= buf.length) + { + throw new IOException("Header exceeded maximum size of 8k."); + } + } + BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, index))); + String line = reader.readLine(); + int x = line.indexOf(' '); + status = Integer.parseInt(line.substring(x + 1 , line.indexOf(' ', x+1))); + while(line != null) + { + int i = line.indexOf(':'); + if(i > 0) + { + header.put(line.substring(0, i), line.substring(i + 1) .trim()); + } + line = reader.readLine(); + } + reader.close(); + } + + @Override + public InputStream getInputStream() throws IOException + { + connect(); + return bin; + } + + @Override + public OutputStream getOutputStream() throws IOException + { + throw new UnsupportedOperationException("Can' write to HTTP Connection"); + } + } } diff --git a/classpath/java/io/BufferedInputStream.java b/classpath/java/io/BufferedInputStream.java index ca4b7033ec..25a7441304 100644 --- a/classpath/java/io/BufferedInputStream.java +++ b/classpath/java/io/BufferedInputStream.java @@ -7,9 +7,11 @@ There is NO WARRANTY for this software. See license.txt for details. */ - package java.io; +import java.io.IOException; +import java.io.InputStream; + public class BufferedInputStream extends InputStream { private final InputStream in; private final byte[] buffer; @@ -25,17 +27,16 @@ public class BufferedInputStream extends InputStream { this(in, 4096); } - private void fill() throws IOException { + private int fill() throws IOException { position = 0; limit = in.read(buffer); + + return limit; } public int read() throws IOException { - if (position >= limit) { - fill(); - if (limit == -1) { - return -1; - } + if (position >= limit && fill() == -1) { + return -1; } return buffer[position++] & 0xFF; @@ -43,7 +44,9 @@ public class BufferedInputStream extends InputStream { public int read(byte[] b, int offset, int length) throws IOException { int count = 0; - + if (position >= limit && fill() == -1) { + return -1; + } if (position < limit) { int remaining = limit - position; if (remaining > length) { @@ -57,8 +60,8 @@ public class BufferedInputStream extends InputStream { offset += remaining; length -= remaining; } - - while (length > 0) { + while (length > 0 && in.available() > 0) + { int c = in.read(b, offset, length); if (c == -1) { if (count == 0) { @@ -69,13 +72,8 @@ public class BufferedInputStream extends InputStream { offset += c; count += c; length -= c; - - if (in.available() <= 0) { - break; - } } } - return count; } @@ -87,3 +85,4 @@ public class BufferedInputStream extends InputStream { in.close(); } } + diff --git a/classpath/java/net/Socket.java b/classpath/java/net/Socket.java index e486f7f4e6..9a32b88f7c 100644 --- a/classpath/java/net/Socket.java +++ b/classpath/java/net/Socket.java @@ -86,18 +86,16 @@ public class Socket implements Closeable, AutoCloseable { @Override public int read(byte[] buffer) throws IOException { + if(buffer.length == 0) return 0; //spec says return 0 if buffer length is zero. int fullSize = buffer.length; - int index = 0; int size; - do { - size = recv(sock, buffer, index, Math.min(fullSize, Socket.BUFFER_SIZE)); - fullSize -= size; - index += size; - } while (fullSize != 0 && size != 0); - return index; + size = recv(sock, buffer, 0, Math.min(fullSize, Socket.BUFFER_SIZE)); + fullSize -= size; + //removed loop, because otherwise interactive protocols will not work. + if(size < 0) throw new IOException("Error while reading stream"); //as the manpage of recv says, a value below zero indicates an error. + if(size == 0) return -1; // if the stream is closed (size == 0), then return -1 to indicate end of stream. + return size; } - - } private class SocketOutputStream extends OutputStream { diff --git a/test/BufferedInputStreamTest.java b/test/BufferedInputStreamTest.java new file mode 100644 index 0000000000..ba978cd5ef --- /dev/null +++ b/test/BufferedInputStreamTest.java @@ -0,0 +1,80 @@ + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.BufferedInputStream; + +/** + * Checks that BufferedInputStream does not block if data is available in it's internal buffer. + */ +public class BufferedInputStreamTest +{ + public static void main(String[] args) throws IOException + { + MyByteArrayStream in = new MyByteArrayStream(new byte[100]); + + BufferedInputStream bin = new BufferedInputStream(in); + //read a single byte to fill the buffer + int b = bin.read(); + byte[] buf = new byte[10]; + //now try to read 10 bytes. this should a least return the content of the buffer. On OpenJDK this are + //4 bytes (the rest of the buffer returned by MyByteArrayStream in the first call). + //It should definately NOT block. + int count = bin.read(buf); + System.out.println("Read bytes: " + count); + } + + /** + * Internal Stream used to show the BufferedInputStream behaviour. + */ + static class MyByteArrayStream extends ByteArrayInputStream + { + boolean stopReading = false; + + /** + * @param buf + */ + public MyByteArrayStream(byte[] buf) + { + super(buf); + } + + /* (non-Javadoc) + * @see java.io.ByteArrayInputStream#read(byte[], int, int) + */ + @Override + public synchronized int read(byte[] b, int off, int len) + { + if(stopReading == false) + { //On the first call 5 bytes are returned. + stopReading = true; + return super.read(b, off, 5); + } + //on all following calls block. The spec says, that a least one byte is returned, if the + //stream is not at EOF. + while(available() == 0) + { + try + { + Thread.sleep(100); + } + catch (InterruptedException e) + { + } + } + return 0; + } + + /* (non-Javadoc) + * @see java.io.ByteArrayInputStream#available() + */ + @Override + public synchronized int available() + { + if(stopReading) + { + return 0; + } + return super.available(); + } + } +}