2013-10-09 01:56:06 +02:00

333 lines
8.0 KiB
Java

/*
* Copyright 2013 Hannes Janetzek
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.tilesource.common;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.URL;
import java.util.zip.InflaterInputStream;
import org.oscim.core.Tile;
import android.os.SystemClock;
import android.util.Log;
public class LwHttp {
private static final String TAG = LwHttp.class.getName();
private final static byte[] HEADER_HTTP_OK = "200 OK".getBytes();
private final static byte[] HEADER_CONTENT_TYPE = "Content-Type".getBytes();
private final static byte[] HEADER_CONTENT_LENGTH = "Content-Length".getBytes();
private final static int RESPONSE_EXPECTED_LIVES = 100;
private final static int RESPONSE_TIMEOUT = 10000;
private final static int BUFFER_SIZE = 1024;
private final byte[] buffer = new byte[BUFFER_SIZE];
private final String mHost;
private final int mPort;
private int mMaxReq = 0;
private Socket mSocket;
private OutputStream mCommandStream;
private InputStream mResponseStream;
private long mLastRequest = 0;
private SocketAddress mSockAddr;
private final byte[] REQUEST_GET_START;
private final byte[] REQUEST_GET_END;
private final byte[] mRequestBuffer;
private final boolean mInflateContent;
private final byte[] mContentType;
private int mContentLength = -1;
public LwHttp(URL url, String contentType, String extension, boolean deflate) {
mContentType = contentType.getBytes();
mInflateContent = deflate;
int port = url.getPort();
if (port < 0)
port = 80;
String host = url.getHost();
String path = url.getPath();
Log.d(TAG, "open database: " + host + " " + port + " " + path);
REQUEST_GET_START = ("GET " + path).getBytes();
REQUEST_GET_END = ("." + extension + " HTTP/1.1" +
"\nHost: " + host +
"\nConnection: Keep-Alive" +
"\n\n").getBytes();
mHost = host;
mPort = port;
mRequestBuffer = new byte[1024];
System.arraycopy(REQUEST_GET_START, 0,
mRequestBuffer, 0, REQUEST_GET_START.length);
}
static class Buffer extends BufferedInputStream {
public Buffer(InputStream is) {
super(is, 4096);
}
@Override
public synchronized int read() throws IOException {
return super.read();
}
@Override
public synchronized int read(byte[] buffer, int offset, int byteCount)
throws IOException {
return super.read(buffer, offset, byteCount);
}
}
public void close() {
if (mSocket != null) {
try {
mSocket.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
mSocket = null;
}
}
}
public InputStream readHeader() throws IOException {
InputStream is = mResponseStream;
is.mark(4096);
byte[] buf = buffer;
boolean first = true;
boolean ok = true;
int read = 0;
int pos = 0;
int end = 0;
int len = 0;
mContentLength = -1;
// header cannot be larger than BUFFER_SIZE for this to work
for (; (pos < read) || ((read < BUFFER_SIZE) &&
(len = is.read(buf, read, BUFFER_SIZE - read)) >= 0); len = 0) {
read += len;
// end of header lines
while (end < read && (buf[end] != '\n'))
end++;
if (buf[end] != '\n')
continue;
if (!ok) {
// ignore until end of header
} else if (first) {
first = false;
// check only for OK ("HTTP/1.? ".length == 9)
if (!check(HEADER_HTTP_OK, buf, pos + 9, end))
ok = false;
} else if (end - pos == 1) {
// empty line (header end)
end += 1;
break;
} else if (check(HEADER_CONTENT_TYPE, buf, pos, end)) {
if (!check(mContentType, buf, pos + HEADER_CONTENT_TYPE.length + 2, end))
ok = false;
} else if (check(HEADER_CONTENT_LENGTH, buf, pos, end)) {
mContentLength = parseInt(pos + HEADER_CONTENT_LENGTH.length + 2, end - 1, buf);
}
if (!ok) {
String line = new String(buf, pos, end - pos - 1);
Log.d(TAG, ">" + line + "< ");
}
pos += (end - pos) + 1;
end = pos;
}
if (!ok)
return null;
// back to start of content
is.reset();
is.mark(0);
is.skip(end);
if (mInflateContent)
return new InflaterInputStream(is);
return is;
}
public boolean sendRequest(Tile tile) throws IOException {
if (mSocket != null && ((mMaxReq-- <= 0)
|| (SystemClock.elapsedRealtime() - mLastRequest > RESPONSE_TIMEOUT))) {
try {
mSocket.close();
} catch (IOException e) {
Log.wtf(TAG, e);
}
// Log.d(TAG, "not alive - recreate connection " + mMaxReq);
mSocket = null;
}
if (mSocket == null) {
lwHttpConnect();
// we know our server
mMaxReq = RESPONSE_EXPECTED_LIVES;
// Log.d(TAG, "create connection");
} else {
// FIXME not sure if this is correct way to drain socket
int avail = mResponseStream.available();
if (avail > 0) {
Log.d(TAG, "Consume left-over bytes: " + avail);
while ((avail = mResponseStream.available()) > 0)
mResponseStream.read(buffer);
}
}
byte[] request = mRequestBuffer;
int pos = REQUEST_GET_START.length;
int newPos = 0;
if ((newPos = formatTilePath(tile, request, pos)) == 0) {
request[pos++] = '/';
pos = writeInt(tile.zoomLevel, pos, request);
request[pos++] = '/';
pos = writeInt(tile.tileX, pos, request);
request[pos++] = '/';
pos = writeInt(tile.tileY, pos, request);
} else {
pos = newPos;
}
int len = REQUEST_GET_END.length;
System.arraycopy(REQUEST_GET_END, 0, request, pos, len);
len += pos;
try {
mCommandStream.write(request, 0, len);
mCommandStream.flush();
return true;
} catch (IOException e) {
Log.d(TAG, "recreate connection");
}
lwHttpConnect();
mCommandStream.write(request, 0, len);
mCommandStream.flush();
return true;
}
private boolean lwHttpConnect() throws IOException {
if (mSockAddr == null)
mSockAddr = new InetSocketAddress(mHost, mPort);
mSocket = new Socket();
mSocket.connect(mSockAddr, 30000);
mSocket.setTcpNoDelay(true);
mCommandStream = mSocket.getOutputStream();
mResponseStream = new BufferedInputStream(mSocket.getInputStream());
return true;
}
// write (positive) integer to byte array
protected static int writeInt(int val, int pos, byte[] buf) {
if (val == 0) {
buf[pos] = '0';
return pos + 1;
}
int i = 0;
for (int n = val; n > 0; n = n / 10, i++)
buf[pos + i] = (byte) ('0' + n % 10);
// reverse bytes
for (int j = pos, end = pos + i - 1, mid = pos + i / 2; j < mid; j++, end--) {
byte tmp = buf[j];
buf[j] = buf[end];
buf[end] = tmp;
}
return pos + i;
}
// parse (positive) integer from byte array
protected static int parseInt(int pos, int end, byte[] buf) {
int val = 0;
for (; pos < end; pos++)
val = val * 10 + (buf[pos]) - '0';
return val;
}
private static boolean check(byte[] string, byte[] buffer,
int position, int available) {
int length = string.length;
if (available - position < length)
return false;
for (int i = 0; i < length; i++)
if (buffer[position + i] != string[i])
return false;
return true;
}
public void requestCompleted() {
mLastRequest = SystemClock.elapsedRealtime();
}
public int getContentLength() {
return mContentLength;
}
/**
* Write custom tile url
*
* @param tile Tile
* @param path to write url string
* @param curPos current position
* @return new position
*/
protected int formatTilePath(Tile tile, byte[] path, int curPos) {
return 0;
}
}