001/*
002 * Copyright 2002-2019 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.concurrent.TimeUnit;
023
024import okhttp3.Cache;
025import okhttp3.OkHttpClient;
026import okhttp3.Request;
027import okhttp3.RequestBody;
028
029import org.springframework.beans.factory.DisposableBean;
030import org.springframework.http.HttpHeaders;
031import org.springframework.http.HttpMethod;
032import org.springframework.lang.Nullable;
033import org.springframework.util.Assert;
034import org.springframework.util.StringUtils;
035
036/**
037 * {@link ClientHttpRequestFactory} implementation that uses
038 * <a href="https://square.github.io/okhttp/">OkHttp</a> 3.x to create requests.
039 *
040 * @author Luciano Leggieri
041 * @author Arjen Poutsma
042 * @author Roy Clarkson
043 * @since 4.3
044 */
045@SuppressWarnings("deprecation")
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         * Set the underlying read timeout in milliseconds.
075         * A value of 0 specifies an infinite timeout.
076         */
077        public void setReadTimeout(int readTimeout) {
078                this.client = this.client.newBuilder()
079                                .readTimeout(readTimeout, TimeUnit.MILLISECONDS)
080                                .build();
081        }
082
083        /**
084         * Set the underlying write timeout in milliseconds.
085         * A value of 0 specifies an infinite timeout.
086         */
087        public void setWriteTimeout(int writeTimeout) {
088                this.client = this.client.newBuilder()
089                                .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS)
090                                .build();
091        }
092
093        /**
094         * Set the underlying connect timeout in milliseconds.
095         * A value of 0 specifies an infinite timeout.
096         */
097        public void setConnectTimeout(int connectTimeout) {
098                this.client = this.client.newBuilder()
099                                .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
100                                .build();
101        }
102
103
104        @Override
105        public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) {
106                return new OkHttp3ClientHttpRequest(this.client, uri, httpMethod);
107        }
108
109        @Override
110        public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) {
111                return new OkHttp3AsyncClientHttpRequest(this.client, uri, httpMethod);
112        }
113
114
115        @Override
116        public void destroy() throws IOException {
117                if (this.defaultClient) {
118                        // Clean up the client if we created it in the constructor
119                        Cache cache = this.client.cache();
120                        if (cache != null) {
121                                cache.close();
122                        }
123                        this.client.dispatcher().executorService().shutdown();
124                        this.client.connectionPool().evictAll();
125                }
126        }
127
128
129        static Request buildRequest(HttpHeaders headers, byte[] content, URI uri, HttpMethod method)
130                        throws MalformedURLException {
131
132                okhttp3.MediaType contentType = getContentType(headers);
133                RequestBody body = (content.length > 0 ||
134                                okhttp3.internal.http.HttpMethod.requiresRequestBody(method.name()) ?
135                                RequestBody.create(contentType, content) : null);
136
137                Request.Builder builder = new Request.Builder().url(uri.toURL()).method(method.name(), body);
138                headers.forEach((headerName, headerValues) -> {
139                        for (String headerValue : headerValues) {
140                                builder.addHeader(headerName, headerValue);
141                        }
142                });
143                return builder.build();
144        }
145
146        @Nullable
147        private static okhttp3.MediaType getContentType(HttpHeaders headers) {
148                String rawContentType = headers.getFirst(HttpHeaders.CONTENT_TYPE);
149                return (StringUtils.hasText(rawContentType) ? okhttp3.MediaType.parse(rawContentType) : null);
150        }
151
152}