/*
 * Copyright 2002-2018 the original author or 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 org.springframework.remoting.httpinvoker;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Locale;
import java.util.zip.GZIPInputStream;

import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.remoting.support.RemoteInvocationResult;

HttpInvokerRequestExecutor implementation that uses standard Java facilities to execute POST requests, without support for HTTP authentication or advanced configuration options.

Designed for easy subclassing, customizing specific template methods. However, consider HttpComponentsHttpInvokerRequestExecutor for more sophisticated needs: The standard HttpURLConnection class is rather limited in its capabilities.

Author:Juergen Hoeller
See Also:
Since:1.1
/** * {@link org.springframework.remoting.httpinvoker.HttpInvokerRequestExecutor} implementation * that uses standard Java facilities to execute POST requests, without support for HTTP * authentication or advanced configuration options. * * <p>Designed for easy subclassing, customizing specific template methods. However, * consider {@code HttpComponentsHttpInvokerRequestExecutor} for more sophisticated needs: * The standard {@link HttpURLConnection} class is rather limited in its capabilities. * * @author Juergen Hoeller * @since 1.1 * @see java.net.HttpURLConnection */
public class SimpleHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor { private int connectTimeout = -1; private int readTimeout = -1;
Set the underlying URLConnection's connect timeout (in milliseconds). A timeout value of 0 specifies an infinite timeout.

Default is the system's default timeout.

See Also:
  • setConnectTimeout.setConnectTimeout(int)
/** * Set the underlying URLConnection's connect timeout (in milliseconds). * A timeout value of 0 specifies an infinite timeout. * <p>Default is the system's default timeout. * @see URLConnection#setConnectTimeout(int) */
public void setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; }
Set the underlying URLConnection's read timeout (in milliseconds). A timeout value of 0 specifies an infinite timeout.

Default is the system's default timeout.

See Also:
  • setReadTimeout.setReadTimeout(int)
/** * Set the underlying URLConnection's read timeout (in milliseconds). * A timeout value of 0 specifies an infinite timeout. * <p>Default is the system's default timeout. * @see URLConnection#setReadTimeout(int) */
public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; }
Execute the given request through a standard HttpURLConnection.

This method implements the basic processing workflow: The actual work happens in this class's template methods.

See Also:
/** * Execute the given request through a standard {@link HttpURLConnection}. * <p>This method implements the basic processing workflow: * The actual work happens in this class's template methods. * @see #openConnection * @see #prepareConnection * @see #writeRequestBody * @see #validateResponse * @see #readResponseBody */
@Override protected RemoteInvocationResult doExecuteRequest( HttpInvokerClientConfiguration config, ByteArrayOutputStream baos) throws IOException, ClassNotFoundException { HttpURLConnection con = openConnection(config); prepareConnection(con, baos.size()); writeRequestBody(config, con, baos); validateResponse(config, con); InputStream responseBody = readResponseBody(config, con); return readRemoteInvocationResult(responseBody, config.getCodebaseUrl()); }
Open an HttpURLConnection for the given remote invocation request.
Params:
  • config – the HTTP invoker configuration that specifies the target service
Throws:
See Also:
Returns:the HttpURLConnection for the given request
/** * Open an {@link HttpURLConnection} for the given remote invocation request. * @param config the HTTP invoker configuration that specifies the * target service * @return the HttpURLConnection for the given request * @throws IOException if thrown by I/O methods * @see java.net.URL#openConnection() */
protected HttpURLConnection openConnection(HttpInvokerClientConfiguration config) throws IOException { URLConnection con = new URL(config.getServiceUrl()).openConnection(); if (!(con instanceof HttpURLConnection)) { throw new IOException( "Service URL [" + config.getServiceUrl() + "] does not resolve to an HTTP connection"); } return (HttpURLConnection) con; }
Prepare the given HTTP connection.

The default implementation specifies POST as method, "application/x-java-serialized-object" as "Content-Type" header, and the given content length as "Content-Length" header.

Params:
  • connection – the HTTP connection to prepare
  • contentLength – the length of the content to send
Throws:
See Also:
/** * Prepare the given HTTP connection. * <p>The default implementation specifies POST as method, * "application/x-java-serialized-object" as "Content-Type" header, * and the given content length as "Content-Length" header. * @param connection the HTTP connection to prepare * @param contentLength the length of the content to send * @throws IOException if thrown by HttpURLConnection methods * @see java.net.HttpURLConnection#setRequestMethod * @see java.net.HttpURLConnection#setRequestProperty */
protected void prepareConnection(HttpURLConnection connection, int contentLength) throws IOException { if (this.connectTimeout >= 0) { connection.setConnectTimeout(this.connectTimeout); } if (this.readTimeout >= 0) { connection.setReadTimeout(this.readTimeout); } connection.setDoOutput(true); connection.setRequestMethod(HTTP_METHOD_POST); connection.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, getContentType()); connection.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH, Integer.toString(contentLength)); LocaleContext localeContext = LocaleContextHolder.getLocaleContext(); if (localeContext != null) { Locale locale = localeContext.getLocale(); if (locale != null) { connection.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE, locale.toLanguageTag()); } } if (isAcceptGzipEncoding()) { connection.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP); } }
Set the given serialized remote invocation as request body.

The default implementation simply write the serialized invocation to the HttpURLConnection's OutputStream. This can be overridden, for example, to write a specific encoding and potentially set appropriate HTTP request headers.

Params:
  • config – the HTTP invoker configuration that specifies the target service
  • con – the HttpURLConnection to write the request body to
  • baos – the ByteArrayOutputStream that contains the serialized RemoteInvocation object
Throws:
See Also:
/** * Set the given serialized remote invocation as request body. * <p>The default implementation simply write the serialized invocation to the * HttpURLConnection's OutputStream. This can be overridden, for example, to write * a specific encoding and potentially set appropriate HTTP request headers. * @param config the HTTP invoker configuration that specifies the target service * @param con the HttpURLConnection to write the request body to * @param baos the ByteArrayOutputStream that contains the serialized * RemoteInvocation object * @throws IOException if thrown by I/O methods * @see java.net.HttpURLConnection#getOutputStream() * @see java.net.HttpURLConnection#setRequestProperty */
protected void writeRequestBody( HttpInvokerClientConfiguration config, HttpURLConnection con, ByteArrayOutputStream baos) throws IOException { baos.writeTo(con.getOutputStream()); }
Validate the given response as contained in the HttpURLConnection object, throwing an exception if it does not correspond to a successful HTTP response.

Default implementation rejects any HTTP status code beyond 2xx, to avoid parsing the response body and trying to deserialize from a corrupted stream.

Params:
  • config – the HTTP invoker configuration that specifies the target service
  • con – the HttpURLConnection to validate
Throws:
See Also:
/** * Validate the given response as contained in the {@link HttpURLConnection} object, * throwing an exception if it does not correspond to a successful HTTP response. * <p>Default implementation rejects any HTTP status code beyond 2xx, to avoid * parsing the response body and trying to deserialize from a corrupted stream. * @param config the HTTP invoker configuration that specifies the target service * @param con the HttpURLConnection to validate * @throws IOException if validation failed * @see java.net.HttpURLConnection#getResponseCode() */
protected void validateResponse(HttpInvokerClientConfiguration config, HttpURLConnection con) throws IOException { if (con.getResponseCode() >= 300) { throw new IOException( "Did not receive successful HTTP response: status code = " + con.getResponseCode() + ", status message = [" + con.getResponseMessage() + "]"); } }
Extract the response body from the given executed remote invocation request.

The default implementation simply reads the serialized invocation from the HttpURLConnection's InputStream. If the response is recognized as GZIP response, the InputStream will get wrapped in a GZIPInputStream.

Params:
  • config – the HTTP invoker configuration that specifies the target service
  • con – the HttpURLConnection to read the response body from
Throws:
See Also:
Returns:an InputStream for the response body
/** * Extract the response body from the given executed remote invocation * request. * <p>The default implementation simply reads the serialized invocation * from the HttpURLConnection's InputStream. If the response is recognized * as GZIP response, the InputStream will get wrapped in a GZIPInputStream. * @param config the HTTP invoker configuration that specifies the target service * @param con the HttpURLConnection to read the response body from * @return an InputStream for the response body * @throws IOException if thrown by I/O methods * @see #isGzipResponse * @see java.util.zip.GZIPInputStream * @see java.net.HttpURLConnection#getInputStream() * @see java.net.HttpURLConnection#getHeaderField(int) * @see java.net.HttpURLConnection#getHeaderFieldKey(int) */
protected InputStream readResponseBody(HttpInvokerClientConfiguration config, HttpURLConnection con) throws IOException { if (isGzipResponse(con)) { // GZIP response found - need to unzip. return new GZIPInputStream(con.getInputStream()); } else { // Plain response found. return con.getInputStream(); } }
Determine whether the given response is a GZIP response.

Default implementation checks whether the HTTP "Content-Encoding" header contains "gzip" (in any casing).

Params:
  • con – the HttpURLConnection to check
/** * Determine whether the given response is a GZIP response. * <p>Default implementation checks whether the HTTP "Content-Encoding" * header contains "gzip" (in any casing). * @param con the HttpURLConnection to check */
protected boolean isGzipResponse(HttpURLConnection con) { String encodingHeader = con.getHeaderField(HTTP_HEADER_CONTENT_ENCODING); return (encodingHeader != null && encodingHeader.toLowerCase().contains(ENCODING_GZIP)); } }