001: /*
002: * Copyright 2002-2006 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: * http://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:
017: package org.springframework.remoting.httpinvoker;
018:
019: import java.io.ByteArrayOutputStream;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.net.HttpURLConnection;
023: import java.net.URL;
024: import java.net.URLConnection;
025: import java.util.zip.GZIPInputStream;
026:
027: import org.springframework.remoting.support.RemoteInvocationResult;
028:
029: /**
030: * HttpInvokerRequestExecutor implementation that uses standard J2SE facilities
031: * to execute POST requests, without support for HTTP authentication or
032: * advanced configuration options.
033: *
034: * <p>Designed for easy subclassing, customizing specific template methods.
035: * However, consider CommonsHttpInvokerRequestExecutor for more sophisticated
036: * needs: The J2SE HttpURLConnection is rather limited in its capabilities.
037: *
038: * @author Juergen Hoeller
039: * @since 1.1
040: * @see CommonsHttpInvokerRequestExecutor
041: * @see java.net.HttpURLConnection
042: */
043: public class SimpleHttpInvokerRequestExecutor extends
044: AbstractHttpInvokerRequestExecutor {
045:
046: /**
047: * Execute the given request through a standard J2SE HttpURLConnection.
048: * <p>This method implements the basic processing workflow:
049: * The actual work happens in this class's template methods.
050: * @see #openConnection
051: * @see #prepareConnection
052: * @see #writeRequestBody
053: * @see #validateResponse
054: * @see #readResponseBody
055: */
056: protected RemoteInvocationResult doExecuteRequest(
057: HttpInvokerClientConfiguration config,
058: ByteArrayOutputStream baos) throws IOException,
059: ClassNotFoundException {
060:
061: HttpURLConnection con = openConnection(config);
062: prepareConnection(con, baos.size());
063: writeRequestBody(config, con, baos);
064: validateResponse(config, con);
065: InputStream responseBody = readResponseBody(config, con);
066:
067: return readRemoteInvocationResult(responseBody, config
068: .getCodebaseUrl());
069: }
070:
071: /**
072: * Open an HttpURLConnection for the given remote invocation request.
073: * @param config the HTTP invoker configuration that specifies the
074: * target service
075: * @return the HttpURLConnection for the given request
076: * @throws IOException if thrown by I/O methods
077: * @see java.net.URL#openConnection()
078: */
079: protected HttpURLConnection openConnection(
080: HttpInvokerClientConfiguration config) throws IOException {
081: URLConnection con = new URL(config.getServiceUrl())
082: .openConnection();
083: if (!(con instanceof HttpURLConnection)) {
084: throw new IOException("Service URL ["
085: + config.getServiceUrl() + "] is not an HTTP URL");
086: }
087: return (HttpURLConnection) con;
088: }
089:
090: /**
091: * Prepare the given HTTP connection.
092: * <p>The default implementation specifies POST as method,
093: * "application/x-java-serialized-object" as "Content-Type" header,
094: * and the given content length as "Content-Length" header.
095: * @param con the HTTP connection to prepare
096: * @param contentLength the length of the content to send
097: * @throws IOException if thrown by HttpURLConnection methods
098: * @see java.net.HttpURLConnection#setRequestMethod
099: * @see java.net.HttpURLConnection#setRequestProperty
100: */
101: protected void prepareConnection(HttpURLConnection con,
102: int contentLength) throws IOException {
103: con.setDoOutput(true);
104: con.setRequestMethod(HTTP_METHOD_POST);
105: con.setRequestProperty(HTTP_HEADER_CONTENT_TYPE,
106: getContentType());
107: con.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH, Integer
108: .toString(contentLength));
109: if (isAcceptGzipEncoding()) {
110: con.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING,
111: ENCODING_GZIP);
112: }
113: }
114:
115: /**
116: * Set the given serialized remote invocation as request body.
117: * <p>The default implementation simply write the serialized invocation to the
118: * HttpURLConnection's OutputStream. This can be overridden, for example, to write
119: * a specific encoding and potentially set appropriate HTTP request headers.
120: * @param config the HTTP invoker configuration that specifies the target service
121: * @param con the HttpURLConnection to write the request body to
122: * @param baos the ByteArrayOutputStream that contains the serialized
123: * RemoteInvocation object
124: * @throws IOException if thrown by I/O methods
125: * @see java.net.HttpURLConnection#getOutputStream()
126: * @see java.net.HttpURLConnection#setRequestProperty
127: */
128: protected void writeRequestBody(
129: HttpInvokerClientConfiguration config,
130: HttpURLConnection con, ByteArrayOutputStream baos)
131: throws IOException {
132:
133: baos.writeTo(con.getOutputStream());
134: }
135:
136: /**
137: * Validate the given response as contained in the HttpURLConnection object,
138: * throwing an exception if it does not correspond to a successful HTTP response.
139: * <p>Default implementation rejects any HTTP status code beyond 2xx, to avoid
140: * parsing the response body and trying to deserialize from a corrupted stream.
141: * @param config the HTTP invoker configuration that specifies the target service
142: * @param con the HttpURLConnection to validate
143: * @throws IOException if validation failed
144: * @see java.net.HttpURLConnection#getResponseCode()
145: */
146: protected void validateResponse(
147: HttpInvokerClientConfiguration config, HttpURLConnection con)
148: throws IOException {
149:
150: if (con.getResponseCode() >= 300) {
151: throw new IOException(
152: "Did not receive successful HTTP response: status code = "
153: + con.getResponseCode()
154: + ", status message = ["
155: + con.getResponseMessage() + "]");
156: }
157: }
158:
159: /**
160: * Extract the response body from the given executed remote invocation
161: * request.
162: * <p>The default implementation simply reads the serialized invocation
163: * from the HttpURLConnection's InputStream. If the response is recognized
164: * as GZIP response, the InputStream will get wrapped in a GZIPInputStream.
165: * @param config the HTTP invoker configuration that specifies the target service
166: * @param con the HttpURLConnection to read the response body from
167: * @return an InputStream for the response body
168: * @throws IOException if thrown by I/O methods
169: * @see #isGzipResponse
170: * @see java.util.zip.GZIPInputStream
171: * @see java.net.HttpURLConnection#getInputStream()
172: * @see java.net.HttpURLConnection#getHeaderField(int)
173: * @see java.net.HttpURLConnection#getHeaderFieldKey(int)
174: */
175: protected InputStream readResponseBody(
176: HttpInvokerClientConfiguration config, HttpURLConnection con)
177: throws IOException {
178:
179: if (isGzipResponse(con)) {
180: // GZIP response found - need to unzip.
181: return new GZIPInputStream(con.getInputStream());
182: } else {
183: // Plain response found.
184: return con.getInputStream();
185: }
186: }
187:
188: /**
189: * Determine whether the given response is a GZIP response.
190: * <p>Default implementation checks whether the HTTP "Content-Encoding"
191: * header contains "gzip" (in any casing).
192: * @param con the HttpURLConnection to check
193: */
194: protected boolean isGzipResponse(HttpURLConnection con) {
195: String encodingHeader = con
196: .getHeaderField(HTTP_HEADER_CONTENT_ENCODING);
197: return (encodingHeader != null && encodingHeader.toLowerCase()
198: .indexOf(ENCODING_GZIP) != -1);
199: }
200:
201: }
|