001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.http.client; 018 019import java.io.IOException; 020import java.net.HttpURLConnection; 021import java.net.Proxy; 022import java.net.URI; 023import java.net.URL; 024import java.net.URLConnection; 025 026import org.springframework.core.task.AsyncListenableTaskExecutor; 027import org.springframework.http.HttpMethod; 028import org.springframework.lang.Nullable; 029import org.springframework.util.Assert; 030 031/** 032 * {@link ClientHttpRequestFactory} implementation that uses standard JDK facilities. 033 * 034 * @author Arjen Poutsma 035 * @author Juergen Hoeller 036 * @since 3.0 037 * @see java.net.HttpURLConnection 038 * @see HttpComponentsClientHttpRequestFactory 039 */ 040@SuppressWarnings("deprecation") 041public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory { 042 043 private static final int DEFAULT_CHUNK_SIZE = 4096; 044 045 046 @Nullable 047 private Proxy proxy; 048 049 private boolean bufferRequestBody = true; 050 051 private int chunkSize = DEFAULT_CHUNK_SIZE; 052 053 private int connectTimeout = -1; 054 055 private int readTimeout = -1; 056 057 private boolean outputStreaming = true; 058 059 @Nullable 060 private AsyncListenableTaskExecutor taskExecutor; 061 062 063 /** 064 * Set the {@link Proxy} to use for this request factory. 065 */ 066 public void setProxy(Proxy proxy) { 067 this.proxy = proxy; 068 } 069 070 /** 071 * Indicate whether this request factory should buffer the 072 * {@linkplain ClientHttpRequest#getBody() request body} internally. 073 * <p>Default is {@code true}. When sending large amounts of data via POST or PUT, 074 * it is recommended to change this property to {@code false}, so as not to run 075 * out of memory. This will result in a {@link ClientHttpRequest} that either 076 * streams directly to the underlying {@link HttpURLConnection} (if the 077 * {@link org.springframework.http.HttpHeaders#getContentLength() Content-Length} 078 * is known in advance), or that will use "Chunked transfer encoding" 079 * (if the {@code Content-Length} is not known in advance). 080 * @see #setChunkSize(int) 081 * @see HttpURLConnection#setFixedLengthStreamingMode(int) 082 */ 083 public void setBufferRequestBody(boolean bufferRequestBody) { 084 this.bufferRequestBody = bufferRequestBody; 085 } 086 087 /** 088 * Set the number of bytes to write in each chunk when not buffering request 089 * bodies locally. 090 * <p>Note that this parameter is only used when 091 * {@link #setBufferRequestBody(boolean) bufferRequestBody} is set to {@code false}, 092 * and the {@link org.springframework.http.HttpHeaders#getContentLength() Content-Length} 093 * is not known in advance. 094 * @see #setBufferRequestBody(boolean) 095 */ 096 public void setChunkSize(int chunkSize) { 097 this.chunkSize = chunkSize; 098 } 099 100 /** 101 * Set the underlying URLConnection's connect timeout (in milliseconds). 102 * A timeout value of 0 specifies an infinite timeout. 103 * <p>Default is the system's default timeout. 104 * @see URLConnection#setConnectTimeout(int) 105 */ 106 public void setConnectTimeout(int connectTimeout) { 107 this.connectTimeout = connectTimeout; 108 } 109 110 /** 111 * Set the underlying URLConnection's read timeout (in milliseconds). 112 * A timeout value of 0 specifies an infinite timeout. 113 * <p>Default is the system's default timeout. 114 * @see URLConnection#setReadTimeout(int) 115 */ 116 public void setReadTimeout(int readTimeout) { 117 this.readTimeout = readTimeout; 118 } 119 120 /** 121 * Set if the underlying URLConnection can be set to 'output streaming' mode. 122 * Default is {@code true}. 123 * <p>When output streaming is enabled, authentication and redirection cannot be handled automatically. 124 * If output streaming is disabled, the {@link HttpURLConnection#setFixedLengthStreamingMode} and 125 * {@link HttpURLConnection#setChunkedStreamingMode} methods of the underlying connection will never 126 * be called. 127 * @param outputStreaming if output streaming is enabled 128 */ 129 public void setOutputStreaming(boolean outputStreaming) { 130 this.outputStreaming = outputStreaming; 131 } 132 133 /** 134 * Set the task executor for this request factory. Setting this property is required 135 * for {@linkplain #createAsyncRequest(URI, HttpMethod) creating asynchronous requests}. 136 * @param taskExecutor the task executor 137 */ 138 public void setTaskExecutor(AsyncListenableTaskExecutor taskExecutor) { 139 this.taskExecutor = taskExecutor; 140 } 141 142 143 @Override 144 public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { 145 HttpURLConnection connection = openConnection(uri.toURL(), this.proxy); 146 prepareConnection(connection, httpMethod.name()); 147 148 if (this.bufferRequestBody) { 149 return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming); 150 } 151 else { 152 return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming); 153 } 154 } 155 156 /** 157 * {@inheritDoc} 158 * <p>Setting the {@link #setTaskExecutor taskExecutor} property is required before calling this method. 159 */ 160 @Override 161 public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException { 162 Assert.state(this.taskExecutor != null, "Asynchronous execution requires TaskExecutor to be set"); 163 164 HttpURLConnection connection = openConnection(uri.toURL(), this.proxy); 165 prepareConnection(connection, httpMethod.name()); 166 167 if (this.bufferRequestBody) { 168 return new SimpleBufferingAsyncClientHttpRequest( 169 connection, this.outputStreaming, this.taskExecutor); 170 } 171 else { 172 return new SimpleStreamingAsyncClientHttpRequest( 173 connection, this.chunkSize, this.outputStreaming, this.taskExecutor); 174 } 175 } 176 177 /** 178 * Opens and returns a connection to the given URL. 179 * <p>The default implementation uses the given {@linkplain #setProxy(java.net.Proxy) proxy} - 180 * if any - to open a connection. 181 * @param url the URL to open a connection to 182 * @param proxy the proxy to use, may be {@code null} 183 * @return the opened connection 184 * @throws IOException in case of I/O errors 185 */ 186 protected HttpURLConnection openConnection(URL url, @Nullable Proxy proxy) throws IOException { 187 URLConnection urlConnection = (proxy != null ? url.openConnection(proxy) : url.openConnection()); 188 if (!HttpURLConnection.class.isInstance(urlConnection)) { 189 throw new IllegalStateException("HttpURLConnection required for [" + url + "] but got: " + urlConnection); 190 } 191 return (HttpURLConnection) urlConnection; 192 } 193 194 /** 195 * Template method for preparing the given {@link HttpURLConnection}. 196 * <p>The default implementation prepares the connection for input and output, and sets the HTTP method. 197 * @param connection the connection to prepare 198 * @param httpMethod the HTTP request method ({@code GET}, {@code POST}, etc.) 199 * @throws IOException in case of I/O errors 200 */ 201 protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { 202 if (this.connectTimeout >= 0) { 203 connection.setConnectTimeout(this.connectTimeout); 204 } 205 if (this.readTimeout >= 0) { 206 connection.setReadTimeout(this.readTimeout); 207 } 208 209 connection.setDoInput(true); 210 211 if ("GET".equals(httpMethod)) { 212 connection.setInstanceFollowRedirects(true); 213 } 214 else { 215 connection.setInstanceFollowRedirects(false); 216 } 217 218 if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) || 219 "PATCH".equals(httpMethod) || "DELETE".equals(httpMethod)) { 220 connection.setDoOutput(true); 221 } 222 else { 223 connection.setDoOutput(false); 224 } 225 226 connection.setRequestMethod(httpMethod); 227 } 228 229}