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
This commit is contained in:
keinhaar 2015-12-20 12:42:36 +01:00
parent 0c71ba0763
commit 524f034bac
4 changed files with 205 additions and 34 deletions

View File

@ -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<String,String> header = new HashMap<String, String>();
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");
}
}
}

View File

@ -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();
}
}

View File

@ -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 {

View File

@ -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();
}
}
}