All Downloads are FREE. Search and download functionalities are using the official Maven repository.

feign.Client Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2012-2019 The Feign Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package feign;

import static java.lang.String.format;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPOutputStream;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import feign.Request.Options;
import static feign.Util.CONTENT_ENCODING;
import static feign.Util.CONTENT_LENGTH;
import static feign.Util.ENCODING_DEFLATE;
import static feign.Util.ENCODING_GZIP;

/**
 * Submits HTTP {@link Request requests}. Implementations are expected to be
 * thread-safe.
 */
public interface Client {

	/**
	 * Executes a request against its {@link Request#url() url} and returns a
	 * response.
	 *
	 * @param request
	 *            safe to replay.
	 * @param options
	 *            options to apply to this request.
	 * @return connected response, {@link Response.Body} is absent or unread.
	 * @throws IOException
	 *             on a network error connecting to {@link Request#url()}.
	 */
	Response execute(Request request, Options options) throws IOException;

	class Default implements Client {

		private final SSLSocketFactory sslContextFactory;
		private final HostnameVerifier hostnameVerifier;

		/**
		 * Null parameters imply platform defaults.
		 */
		public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
			this.sslContextFactory = sslContextFactory;
			this.hostnameVerifier = hostnameVerifier;
		}

		@Override
		public Response execute(Request request, Options options) throws IOException {
			HttpURLConnection connection = convertAndSend(request, options);
			return convertResponse(connection, request);
		}

		HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
			final HttpURLConnection connection = (HttpURLConnection) new URL(request.url()).openConnection();
			if (connection instanceof HttpsURLConnection) {
				HttpsURLConnection sslCon = (HttpsURLConnection) connection;
				if (sslContextFactory != null) {
					sslCon.setSSLSocketFactory(sslContextFactory);
				}
				if (hostnameVerifier != null) {
					sslCon.setHostnameVerifier(hostnameVerifier);
				}
			}
			connection.setConnectTimeout(options.connectTimeoutMillis());
			connection.setReadTimeout(options.readTimeoutMillis());
			connection.setAllowUserInteraction(false);
			connection.setInstanceFollowRedirects(options.isFollowRedirects());
			connection.setRequestMethod(request.httpMethod().name());

			Collection contentEncodingValues = request.headers().get(CONTENT_ENCODING);
			boolean gzipEncodedRequest = contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
			boolean deflateEncodedRequest = contentEncodingValues != null
					&& contentEncodingValues.contains(ENCODING_DEFLATE);

			boolean hasAcceptHeader = false;
			Integer contentLength = null;
			for (String field : request.headers().keySet()) {
				if (field.equalsIgnoreCase("Accept")) {
					hasAcceptHeader = true;
				}
				for (String value : request.headers().get(field)) {
					if (field.equals(CONTENT_LENGTH)) {
						if (!gzipEncodedRequest && !deflateEncodedRequest) {
							contentLength = Integer.valueOf(value);
							connection.addRequestProperty(field, value);
						}
					} else {
						connection.addRequestProperty(field, value);
					}
				}
			}
			// Some servers choke on the default accept string.
			if (!hasAcceptHeader) {
				connection.addRequestProperty("Accept", "*/*");
			}

			if (request.requestBody().asBytes() != null) {
				if (contentLength != null) {
					connection.setFixedLengthStreamingMode(contentLength);
				} else {
					connection.setChunkedStreamingMode(8196);
				}
				connection.setDoOutput(true);
				OutputStream out = connection.getOutputStream();
				if (gzipEncodedRequest) {
					out = new GZIPOutputStream(out);
				} else if (deflateEncodedRequest) {
					out = new DeflaterOutputStream(out);
				}
				try {
					out.write(request.requestBody().asBytes());
				} finally {
					try {
						out.close();
					} catch (IOException suppressed) { // NOPMD
					}
				}
			}
			return connection;
		}

		Response convertResponse(HttpURLConnection connection, Request request) throws IOException {
			int status = connection.getResponseCode();
			String reason = connection.getResponseMessage();

			if (status < 0) {
				throw new IOException(format("Invalid status(%s) executing %s %s", status,
						connection.getRequestMethod(), connection.getURL()));
			}

			Map> headers = new LinkedHashMap>();
			for (Map.Entry> field : connection.getHeaderFields().entrySet()) {
				// response message
				if (field.getKey() != null) {
					headers.put(field.getKey(), field.getValue());
				}
			}

			Integer length = connection.getContentLength();
			if (length == -1) {
				length = null;
			}
			InputStream stream;
			if (status >= 400) {
				stream = connection.getErrorStream();
			} else {
				stream = connection.getInputStream();
			}
			return Response.builder().status(status).reason(reason).headers(headers).request(request)
					.body(stream, length).build();
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy