/* * The MIT License * * Copyright (c) 2013 The Broad Institute * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package htsjdk.samtools.seekablestream; import htsjdk.samtools.util.HttpUtils; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URL; /** * @author jrobinso */ public class SeekableHTTPStream extends SeekableStream { private long position = 0; private long contentLength = -1; private final URL url; private final Proxy proxy; public SeekableHTTPStream(final URL url) { this(url, null); } public SeekableHTTPStream(final URL url, Proxy proxy) { this.proxy = proxy; this.url = url; // Try to get the file length // Note: This also sets setDefaultUseCaches(false), which is important final String contentLengthString = HttpUtils.getHeaderField(url, "Content-Length"); if (contentLengthString != null) { try { contentLength = Long.parseLong(contentLengthString); } catch (NumberFormatException ignored) { System.err.println("WARNING: Invalid content length (" + contentLengthString + " for: " + url); contentLength = -1; } } } public long position() { return position; } public long length() { return contentLength; } @Override public long skip(long n) throws IOException { long bytesToSkip = Math.min(n, contentLength - position); position += bytesToSkip; return bytesToSkip; } public boolean eof() throws IOException { return contentLength > 0 && position >= contentLength; } public void seek(final long position) { this.position = position; } public int read(byte[] buffer, int offset, int len) throws IOException { if (offset < 0 || len < 0 || (offset + len) > buffer.length) { throw new IndexOutOfBoundsException("Offset="+offset+",len="+len+",buflen="+buffer.length); } if (len == 0 || position == contentLength) { return 0; } HttpURLConnection connection = null; InputStream is = null; String byteRange = ""; int n = 0; try { connection = proxy == null ? (HttpURLConnection) url.openConnection() : (HttpURLConnection) url.openConnection(proxy); long endRange = position + len - 1; // IF we know the total content length, limit the end range to that. if (contentLength > 0) { endRange = Math.min(endRange, contentLength); } byteRange = "bytes=" + position + "-" + endRange; connection.setRequestProperty("Range", byteRange); is = connection.getInputStream(); while (n < len) { int count = is.read(buffer, offset + n, len - n); if (count < 0) { if (n == 0) { return -1; } else { break; } } n += count; } position += n; return n; } catch (IOException e) { // THis is a bit of a hack, but its not clear how else to handle this. If a byte range is specified // that goes past the end of the file the response code will be 416. The MAC os translates this to // an IOException with the 416 code in the message. Windows translates the error to an EOFException. // // The BAM file iterator uses the return value to detect end of file (specifically looks for n == 0). if (e.getMessage().contains("416") || (e instanceof EOFException)) { if (n == 0) { return -1; } else { position += n; // As we are at EOF, the contentLength and position are by definition = contentLength = position; return n; } } else { throw e; } } finally { if (is != null) { is.close(); } if (connection != null) { connection.disconnect(); } } } public void close() throws IOException { // Nothing to do } public int read() throws IOException { byte []tmp=new byte[1]; read(tmp,0,1); return (int) tmp[0] & 0xFF; } @Override public String getSource() { return url.toString(); } }