001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.vfs2.provider.http5; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.net.URI; 022 023import org.apache.commons.vfs2.FileContentInfoFactory; 024import org.apache.commons.vfs2.FileNotFoundException; 025import org.apache.commons.vfs2.FileSystemException; 026import org.apache.commons.vfs2.FileSystemOptions; 027import org.apache.commons.vfs2.FileType; 028import org.apache.commons.vfs2.RandomAccessContent; 029import org.apache.commons.vfs2.provider.AbstractFileName; 030import org.apache.commons.vfs2.provider.AbstractFileObject; 031import org.apache.commons.vfs2.provider.GenericURLFileName; 032import org.apache.commons.vfs2.util.RandomAccessMode; 033import org.apache.hc.client5.http.classic.methods.HttpGet; 034import org.apache.hc.client5.http.classic.methods.HttpHead; 035import org.apache.hc.client5.http.classic.methods.HttpUriRequest; 036import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 037import org.apache.hc.client5.http.protocol.HttpClientContext; 038import org.apache.hc.client5.http.utils.DateUtils; 039import org.apache.hc.core5.http.ClassicHttpResponse; 040import org.apache.hc.core5.http.Header; 041import org.apache.hc.core5.http.HttpHeaders; 042import org.apache.hc.core5.http.HttpResponse; 043import org.apache.hc.core5.http.HttpStatus; 044 045/** 046 * A file object backed by Apache HttpComponents HttpClient v5. 047 * 048 * @param <FS> An {@link Http5FileSystem} subclass 049 * @since 2.5.0 050 */ 051public class Http5FileObject<FS extends Http5FileSystem> extends AbstractFileObject<FS> { 052 053 /** 054 * URL charset string. 055 */ 056 private final String urlCharset; 057 058 /** 059 * Internal URI mapped to this {@code FileObject}. 060 * For example, the internal URI of {@code http4://example.com/a.txt} is {@code http://example.com/a.txt}. 061 */ 062 private final URI internalURI; 063 064 /** 065 * The last executed HEAD {@code HttpResponse} object. 066 */ 067 private HttpResponse lastHeadResponse; 068 069 /** 070 * Constructs {@code Http4FileObject}. 071 * 072 * @param name file name 073 * @param fileSystem file system 074 * @throws FileSystemException if any error occurs 075 */ 076 protected Http5FileObject(final AbstractFileName name, final FS fileSystem) 077 throws FileSystemException { 078 this(name, fileSystem, Http5FileSystemConfigBuilder.getInstance()); 079 } 080 081 /** 082 * Constructs {@code Http4FileObject}. 083 * 084 * @param name file name 085 * @param fileSystem file system 086 * @param builder {@code Http4FileSystemConfigBuilder} object 087 * @throws FileSystemException if any error occurs 088 */ 089 protected Http5FileObject(final AbstractFileName name, final FS fileSystem, 090 final Http5FileSystemConfigBuilder builder) throws FileSystemException { 091 super(name, fileSystem); 092 final FileSystemOptions fileSystemOptions = fileSystem.getFileSystemOptions(); 093 urlCharset = builder.getUrlCharset(fileSystemOptions); 094 final String pathEncoded = ((GenericURLFileName) name).getPathQueryEncoded(getUrlCharset()); 095 internalURI = fileSystem.getInternalBaseURI().resolve(pathEncoded); 096 } 097 098 @Override 099 protected void doDetach() throws Exception { 100 lastHeadResponse = null; 101 } 102 103 @Override 104 protected long doGetContentSize() throws Exception { 105 if (lastHeadResponse == null) { 106 return 0L; 107 } 108 109 final Header header = lastHeadResponse.getFirstHeader(HttpHeaders.CONTENT_LENGTH); 110 111 if (header == null) { 112 // Assume 0 content-length 113 return 0; 114 } 115 116 return Long.parseLong(header.getValue()); 117 } 118 119 @Override 120 protected InputStream doGetInputStream(final int bufferSize) throws Exception { 121 final HttpGet getRequest = new HttpGet(getInternalURI()); 122 final ClassicHttpResponse httpResponse = executeHttpUriRequest(getRequest); 123 final int status = httpResponse.getCode(); 124 125 if (status == HttpStatus.SC_NOT_FOUND) { 126 throw new FileNotFoundException(getName()); 127 } 128 129 if (status != HttpStatus.SC_OK) { 130 throw new FileSystemException("vfs.provider.http/get.error", getName(), Integer.valueOf(status)); 131 } 132 133 return new MonitoredHttpResponseContentInputStream(httpResponse, bufferSize); 134 } 135 136 @Override 137 protected long doGetLastModifiedTime() throws Exception { 138 FileSystemException.requireNonNull(lastHeadResponse, "vfs.provider.http/last-modified.error", getName()); 139 140 final Header header = lastHeadResponse.getFirstHeader("Last-Modified"); 141 142 FileSystemException.requireNonNull(header, "vfs.provider.http/last-modified.error", getName()); 143 144 return DateUtils.parseStandardDate(header.getValue()).toEpochMilli(); 145 } 146 147 @Override 148 protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception { 149 return new Http5RandomAccessContent<>(this, mode); 150 } 151 152 @Override 153 protected FileType doGetType() throws Exception { 154 lastHeadResponse = executeHttpUriRequest(new HttpHead(getInternalURI())); 155 final int status = lastHeadResponse.getCode(); 156 157 if (status == HttpStatus.SC_OK 158 || status == HttpStatus.SC_METHOD_NOT_ALLOWED /* method is not allowed, but resource exist */) { 159 return FileType.FILE; 160 } 161 if (status == HttpStatus.SC_NOT_FOUND || status == HttpStatus.SC_GONE) { 162 return FileType.IMAGINARY; 163 } 164 throw new FileSystemException("vfs.provider.http/head.error", getName(), Integer.valueOf(status)); 165 } 166 167 @Override 168 protected boolean doIsWriteable() throws Exception { 169 return false; 170 } 171 172 @Override 173 protected String[] doListChildren() throws Exception { 174 throw new UnsupportedOperationException("Not implemented."); 175 } 176 177 /** 178 * Execute the request using the given {@code httpRequest} and return a {@code ClassicHttpResponse} from the execution. 179 * 180 * @param httpRequest {@code HttpUriRequest} object 181 * @return {@code ClassicHttpResponse} from the execution 182 * @throws IOException if IO error occurs 183 */ 184 protected ClassicHttpResponse executeHttpUriRequest(final HttpUriRequest httpRequest) throws IOException { 185 final CloseableHttpClient httpClient = (CloseableHttpClient) getAbstractFileSystem().getHttpClient(); 186 final HttpClientContext httpClientContext = getAbstractFileSystem().getHttpClientContext(); 187 return httpClient.execute(httpRequest, httpClientContext); 188 } 189 190 @Override 191 protected FileContentInfoFactory getFileContentInfoFactory() { 192 return new Http5FileContentInfoFactory(); 193 } 194 195 /** 196 * Gets the internal {@code URI} object mapped to this file object. 197 * 198 * @return the internal {@code URI} object mapped to this file object 199 */ 200 protected URI getInternalURI() { 201 return internalURI; 202 } 203 204 /** 205 * Gets the last executed HEAD {@code HttpResponse} object. 206 * 207 * @return the last executed HEAD {@code HttpResponse} object 208 * @throws IOException if IO error occurs 209 */ 210 HttpResponse getLastHeadResponse() throws IOException { 211 if (lastHeadResponse != null) { 212 return lastHeadResponse; 213 } 214 215 return executeHttpUriRequest(new HttpHead(getInternalURI())); 216 } 217 218 /** 219 * Gets URL charset string. 220 * @return URL charset string 221 */ 222 protected String getUrlCharset() { 223 return urlCharset; 224 } 225 226}