/* * myLib - https://github.com/taktod/myLib * Copyright (c) 2014 ttProject. All rights reserved. * * Licensed under The MIT license. */ package com.ttProject.nio.channels; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import org.apache.log4j.Logger; /** * file Channel for remote file via http. * requires range request(206) * @author taktod */ public class URLFileReadChannel implements IFileReadChannel { /** logger */ private static final Logger logger = Logger.getLogger(URLFileReadChannel.class); /** target URI */ private final URL url; /** connection */ private HttpURLConnection conn; /** size information */ private final int size; /** range request start position */ private int startPos; /** read data size */ private int readSize; /** open flag */ private boolean isOpen; /** target channel */ private ReadableByteChannel channel; /** * constructor * @param urlString * @throws IOException */ public URLFileReadChannel(String urlString) throws IOException { this(urlString, 0); } /** * constructor with position * if position is out of range request, throws Exception * @param urlString * @param position * @throws IOException */ public URLFileReadChannel(String urlString, int position) throws IOException { url = new URL(urlString); openConnection(position); // try to take contentLength int size = conn.getContentLength(); if(size < 0) { // in the case of over 2G file. // try to take from header field. long lsize = Long.parseLong(conn.getHeaderField("Content-Length")); if(lsize > Integer.MAX_VALUE) { size = Integer.MAX_VALUE; } } this.size = size; } /** * open connection * @param position * @throws IOException */ private void openConnection(int position) throws IOException { if(size == 0 || position < size) { URLConnection urlConn = url.openConnection(); if(!(urlConn instanceof HttpURLConnection)) { logger.error("connection is not via http"); throw new IOException("connection is not http"); } conn = (HttpURLConnection)urlConn; conn.setRequestMethod("GET"); conn.setAllowUserInteraction(false); conn.setInstanceFollowRedirects(true); // if position is not 0, use range request. if(position != 0) { conn.addRequestProperty("Range", "bytes=" + position + "-"); } // access as iPhone // conn.setRequestProperty("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 6_1 like Mac OS X; ja-jp) AppleWebKit/536.26 (KHTML, like Gecko) CriOS/23.0.1271.100 Mobile/10B142 Safari/8536.25"); // access as chrome conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.63 Safari/537.36"); // let the server know proxy access. conn.setRequestProperty("Via", "1.1(jetty)"); conn.connect(); isOpen = true; channel = Channels.newChannel(conn.getInputStream()); } else { isOpen = false; if(position > size) { logger.error("out of range for http."); throw new IOException("out of range for http"); } } // update information startPos = position; readSize = 0; } /** * close connection */ private void closeConnection() { conn.disconnect(); isOpen = false; } /** * {@inheritDoc} */ public boolean isOpen() { return isOpen; } /** * {@inheritDoc} */ public int read(ByteBuffer dst) throws IOException { int startPos = dst.position(); channel.read(dst); int readSize = dst.position() - startPos; this.readSize += readSize; return readSize; } /** * response contents type. * @return */ public String getContentType() { return conn.getContentType(); } /** * {@inheritDoc} */ public void close() throws IOException { closeConnection(); } /** * {@inheritDoc} */ public int position() { return startPos + readSize; } /** * {@inheritDoc} */ public URLFileReadChannel position(int newPosition) throws IOException { if(newPosition == size) { readSize = newPosition - startPos; } if(!isOpen) { return this; } // calcurate skip size. long skipSize = newPosition - startPos - readSize; if(skipSize == 0) { // noskip return this; } // skip size is already downloaded. if(skipSize > 0 && conn.getInputStream().available() > skipSize) { readSize += skipSize; conn.getInputStream().skip(skipSize); } else { // skip size is not downloaded yet or rewind. use range connection to get data. logger.info("to change position, need to re-connect http."); closeConnection(); openConnection(newPosition); } return this; } /** * {@inheritDoc} * MEMO For dynamic connection(like php site.). there is no size response. in this case size is -1. */ public int size() { return size; } /** * {@inheritDoc} */ @Override public String getUri() { return url.toString(); } }