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}