001/*
002 * Copyright 2002-2018 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.MalformedURLException;
021import java.net.URI;
022import java.util.List;
023import java.util.Map;
024import java.util.concurrent.TimeUnit;
025
026import okhttp3.Cache;
027import okhttp3.OkHttpClient;
028import okhttp3.Request;
029import okhttp3.RequestBody;
030
031import org.springframework.beans.factory.DisposableBean;
032import org.springframework.http.HttpHeaders;
033import org.springframework.http.HttpMethod;
034import org.springframework.util.Assert;
035import org.springframework.util.StringUtils;
036
037/**
038 * {@link ClientHttpRequestFactory} implementation that uses
039 * <a href="https://square.github.io/okhttp/">OkHttp</a> 3.x to create requests.
040 *
041 * @author Luciano Leggieri
042 * @author Arjen Poutsma
043 * @author Roy Clarkson
044 * @since 4.3
045 */
046public class OkHttp3ClientHttpRequestFactory
047                implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory, DisposableBean {
048
049        private OkHttpClient client;
050
051        private final boolean defaultClient;
052
053
054        /**
055         * Create a factory with a default {@link OkHttpClient} instance.
056         */
057        public OkHttp3ClientHttpRequestFactory() {
058                this.client = new OkHttpClient();
059                this.defaultClient = true;
060        }
061
062        /**
063         * Create a factory with the given {@link OkHttpClient} instance.
064         * @param client the client to use
065         */
066        public OkHttp3ClientHttpRequestFactory(OkHttpClient client) {
067                Assert.notNull(client, "OkHttpClient must not be null");
068                this.client = client;
069                this.defaultClient = false;
070        }
071
072
073        /**
074         * Sets the underlying read timeout in milliseconds.
075         * A value of 0 specifies an infinite timeout.
076         * @see OkHttpClient.Builder#readTimeout(long, TimeUnit)
077         */
078        public void setReadTimeout(int readTimeout) {
079                this.client = this.client.newBuilder()
080                                .readTimeout(readTimeout, TimeUnit.MILLISECONDS)
081                                .build();
082        }
083
084        /**
085         * Sets the underlying write timeout in milliseconds.
086         * A value of 0 specifies an infinite timeout.
087         * @see OkHttpClient.Builder#writeTimeout(long, TimeUnit)
088         */
089        public void setWriteTimeout(int writeTimeout) {
090                this.client = this.client.newBuilder()
091                                .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS)
092                                .build();
093        }
094
095        /**
096         * Sets the underlying connect timeout in milliseconds.
097         * A value of 0 specifies an infinite timeout.
098         * @see OkHttpClient.Builder#connectTimeout(long, TimeUnit)
099         */
100        public void setConnectTimeout(int connectTimeout) {
101                this.client = this.client.newBuilder()
102                                .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
103                                .build();
104        }
105
106
107        @Override
108        public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) {
109                return new OkHttp3ClientHttpRequest(this.client, uri, httpMethod);
110        }
111
112        @Override
113        public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) {
114                return new OkHttp3AsyncClientHttpRequest(this.client, uri, httpMethod);
115        }
116
117
118        @Override
119        public void destroy() throws IOException {
120                if (this.defaultClient) {
121                        // Clean up the client if we created it in the constructor
122                        Cache cache = this.client.cache();
123                        if (cache != null) {
124                                cache.close();
125                        }
126                        this.client.dispatcher().executorService().shutdown();
127                }
128        }
129
130
131        static Request buildRequest(HttpHeaders headers, byte[] content, URI uri, HttpMethod method)
132                        throws MalformedURLException {
133
134                okhttp3.MediaType contentType = getContentType(headers);
135                RequestBody body = (content.length > 0 ||
136                                okhttp3.internal.http.HttpMethod.requiresRequestBody(method.name()) ?
137                                RequestBody.create(contentType, content) : null);
138
139                Request.Builder builder = new Request.Builder().url(uri.toURL()).method(method.name(), body);
140                for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
141                        String headerName = entry.getKey();
142                        for (String headerValue : entry.getValue()) {
143                                builder.addHeader(headerName, headerValue);
144                        }
145                }
146                return builder.build();
147        }
148
149        private static okhttp3.MediaType getContentType(HttpHeaders headers) {
150                String rawContentType = headers.getFirst(HttpHeaders.CONTENT_TYPE);
151                return (StringUtils.hasText(rawContentType) ? okhttp3.MediaType.parse(rawContentType) : null);
152        }
153
154}