001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package org.apache.hadoop.hdfs; 020 021 import java.io.IOException; 022 import java.io.InputStream; 023 import java.net.HttpURLConnection; 024 import java.net.URL; 025 026 import org.apache.hadoop.fs.FSInputStream; 027 import org.apache.hadoop.hdfs.server.namenode.StreamFile; 028 029 /** 030 * To support HTTP byte streams, a new connection to an HTTP server needs to be 031 * created each time. This class hides the complexity of those multiple 032 * connections from the client. Whenever seek() is called, a new connection 033 * is made on the successive read(). The normal input stream functions are 034 * connected to the currently active input stream. 035 */ 036 public abstract class ByteRangeInputStream extends FSInputStream { 037 038 /** 039 * This class wraps a URL and provides method to open connection. 040 * It can be overridden to change how a connection is opened. 041 */ 042 public static abstract class URLOpener { 043 protected URL url; 044 045 public URLOpener(URL u) { 046 url = u; 047 } 048 049 public void setURL(URL u) { 050 url = u; 051 } 052 053 public URL getURL() { 054 return url; 055 } 056 057 protected abstract HttpURLConnection openConnection() throws IOException; 058 059 protected abstract HttpURLConnection openConnection(final long offset) throws IOException; 060 } 061 062 enum StreamStatus { 063 NORMAL, SEEK 064 } 065 protected InputStream in; 066 protected URLOpener originalURL; 067 protected URLOpener resolvedURL; 068 protected long startPos = 0; 069 protected long currentPos = 0; 070 protected long filelength; 071 072 StreamStatus status = StreamStatus.SEEK; 073 074 /** 075 * Create with the specified URLOpeners. Original url is used to open the 076 * stream for the first time. Resolved url is used in subsequent requests. 077 * @param o Original url 078 * @param r Resolved url 079 */ 080 public ByteRangeInputStream(URLOpener o, URLOpener r) { 081 this.originalURL = o; 082 this.resolvedURL = r; 083 } 084 085 protected abstract void checkResponseCode(final HttpURLConnection connection 086 ) throws IOException; 087 088 protected abstract URL getResolvedUrl(final HttpURLConnection connection 089 ) throws IOException; 090 091 private InputStream getInputStream() throws IOException { 092 if (status != StreamStatus.NORMAL) { 093 094 if (in != null) { 095 in.close(); 096 in = null; 097 } 098 099 // Use the original url if no resolved url exists, eg. if 100 // it's the first time a request is made. 101 final URLOpener opener = 102 (resolvedURL.getURL() == null) ? originalURL : resolvedURL; 103 104 final HttpURLConnection connection = opener.openConnection(startPos); 105 connection.connect(); 106 checkResponseCode(connection); 107 108 final String cl = connection.getHeaderField(StreamFile.CONTENT_LENGTH); 109 filelength = (cl == null) ? -1 : Long.parseLong(cl); 110 in = connection.getInputStream(); 111 112 resolvedURL.setURL(getResolvedUrl(connection)); 113 status = StreamStatus.NORMAL; 114 } 115 116 return in; 117 } 118 119 private void update(final boolean isEOF, final int n) 120 throws IOException { 121 if (!isEOF) { 122 currentPos += n; 123 } else if (currentPos < filelength) { 124 throw new IOException("Got EOF but currentPos = " + currentPos 125 + " < filelength = " + filelength); 126 } 127 } 128 129 public int read() throws IOException { 130 final int b = getInputStream().read(); 131 update(b == -1, 1); 132 return b; 133 } 134 135 /** 136 * Seek to the given offset from the start of the file. 137 * The next read() will be from that location. Can't 138 * seek past the end of the file. 139 */ 140 public void seek(long pos) throws IOException { 141 if (pos != currentPos) { 142 startPos = pos; 143 currentPos = pos; 144 status = StreamStatus.SEEK; 145 } 146 } 147 148 /** 149 * Return the current offset from the start of the file 150 */ 151 public long getPos() throws IOException { 152 return currentPos; 153 } 154 155 /** 156 * Seeks a different copy of the data. Returns true if 157 * found a new source, false otherwise. 158 */ 159 public boolean seekToNewSource(long targetPos) throws IOException { 160 return false; 161 } 162 }