001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2016, Connect2id Ltd. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * 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 distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.jose.util; 019 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.net.HttpURLConnection; 024import java.net.URL; 025import java.nio.charset.Charset; 026 027import net.jcip.annotations.ThreadSafe; 028 029 030/** 031 * The default retriever of resources specified by URL. Provides setting of 032 * HTTP connect and read timeouts as well as a size limit of the retrieved 033 * entity. Caching header directives are not honoured. 034 * 035 * @author Vladimir Dzhuvinov 036 * @version 2018-01-04 037 */ 038@ThreadSafe 039public class DefaultResourceRetriever extends AbstractRestrictedResourceRetriever implements RestrictedResourceRetriever { 040 041 042 /** 043 * If {@code true} the disconnect method of the underlying 044 * HttpURLConnection is called after a successful or failed retrieval. 045 */ 046 private boolean disconnectAfterUse; 047 048 049 /** 050 * Creates a new resource retriever. The HTTP timeouts and entity size 051 * limit are set to zero (infinite). 052 */ 053 public DefaultResourceRetriever() { 054 055 this(0, 0); 056 } 057 058 059 /** 060 * Creates a new resource retriever. The HTTP entity size limit is set 061 * to zero (infinite). 062 * 063 * @param connectTimeout The HTTP connects timeout, in milliseconds, 064 * zero for infinite. Must not be negative. 065 * @param readTimeout The HTTP read timeout, in milliseconds, zero 066 * for infinite. Must not be negative. 067 */ 068 public DefaultResourceRetriever(final int connectTimeout, final int readTimeout) { 069 070 this(connectTimeout, readTimeout, 0); 071 } 072 073 074 /** 075 * Creates a new resource retriever. 076 * 077 * @param connectTimeout The HTTP connects timeout, in milliseconds, 078 * zero for infinite. Must not be negative. 079 * @param readTimeout The HTTP read timeout, in milliseconds, zero 080 * for infinite. Must not be negative. 081 * @param sizeLimit The HTTP entity size limit, in bytes, zero for 082 * infinite. Must not be negative. 083 */ 084 public DefaultResourceRetriever(final int connectTimeout, final int readTimeout, final int sizeLimit) { 085 086 this(connectTimeout, readTimeout, sizeLimit, true); 087 } 088 089 090 /** 091 * Creates a new resource retriever. 092 * 093 * @param connectTimeout The HTTP connects timeout, in 094 * milliseconds, zero for infinite. Must not 095 * be negative. 096 * @param readTimeout The HTTP read timeout, in milliseconds, 097 * zero for infinite. Must not be negative. 098 * @param sizeLimit The HTTP entity size limit, in bytes, zero 099 * for infinite. Must not be negative. 100 * @param disconnectAfterUse If {@code true} the disconnect method of 101 * the underlying {@link HttpURLConnection} 102 * will be called after trying to retrieve 103 * the resource. Whether the TCP socket is 104 * actually closed or reused depends on the 105 * underlying HTTP implementation and the 106 * setting of the {@code keep.alive} system 107 * property. 108 */ 109 public DefaultResourceRetriever(final int connectTimeout, 110 final int readTimeout, 111 final int sizeLimit, 112 final boolean disconnectAfterUse) { 113 114 super(connectTimeout, readTimeout, sizeLimit); 115 this.disconnectAfterUse = disconnectAfterUse; 116 } 117 118 119 /** 120 * Returns {@code true} if the disconnect method of the underlying 121 * {@link HttpURLConnection} will be called after trying to retrieve 122 * the resource. Whether the TCP socket is actually closed or reused 123 * depends on the underlying HTTP implementation and the setting of the 124 * {@code keep.alive} system property. 125 * 126 * @return If {@code true} the disconnect method of the underlying 127 * {@link HttpURLConnection} will be called after trying to 128 * retrieve the resource. 129 */ 130 public boolean disconnectsAfterUse() { 131 132 return disconnectAfterUse; 133 } 134 135 136 /** 137 * Controls calling of the disconnect method the underlying 138 * {@link HttpURLConnection} after trying to retrieve the resource. 139 * Whether the TCP socket is actually closed or reused depends on the 140 * underlying HTTP implementation and the setting of the 141 * {@code keep.alive} system property. 142 * 143 * @return If {@code true} the disconnect method of the underlying 144 * {@link HttpURLConnection} will be called after trying to 145 * retrieve the resource. 146 */ 147 public void setDisconnectsAfterUse(final boolean disconnectAfterUse) { 148 149 this.disconnectAfterUse = disconnectAfterUse; 150 } 151 152 153 @Override 154 public Resource retrieveResource(final URL url) 155 throws IOException { 156 157 HttpURLConnection con = null; 158 try { 159 con = (HttpURLConnection)url.openConnection(); 160 161 con.setConnectTimeout(getConnectTimeout()); 162 con.setReadTimeout(getReadTimeout()); 163 164 final String content; 165 166 InputStream inputStream = con.getInputStream(); 167 try { 168 if (getSizeLimit() > 0) { 169 inputStream = new BoundedInputStream(inputStream, getSizeLimit()); 170 } 171 172 content = IOUtils.readInputStreamToString(inputStream, Charset.forName("UTF-8")); 173 174 } finally { 175 inputStream.close(); 176 } 177 178 // Check HTTP code + message 179 final int statusCode = con.getResponseCode(); 180 final String statusMessage = con.getResponseMessage(); 181 182 // Ensure 2xx status code 183 if (statusCode > 299 || statusCode < 200) { 184 throw new IOException("HTTP " + statusCode + ": " + statusMessage); 185 } 186 187 return new Resource(content, con.getContentType()); 188 189 } catch (ClassCastException e) { 190 throw new IOException("Couldn't open HTTP(S) connection: " + e.getMessage(), e); 191 } finally { 192 if (disconnectAfterUse && con != null) { 193 con.disconnect(); 194 } 195 } 196 } 197}