001/* 002 * Copyright 2002-2017 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.remoting.httpinvoker; 018 019import java.io.ByteArrayOutputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.ObjectInputStream; 023import java.io.ObjectOutputStream; 024import java.io.OutputStream; 025import java.rmi.RemoteException; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029 030import org.springframework.beans.factory.BeanClassLoaderAware; 031import org.springframework.remoting.rmi.CodebaseAwareObjectInputStream; 032import org.springframework.remoting.support.RemoteInvocation; 033import org.springframework.remoting.support.RemoteInvocationResult; 034import org.springframework.util.Assert; 035import org.springframework.util.ClassUtils; 036 037/** 038 * Abstract base implementation of the HttpInvokerRequestExecutor interface. 039 * 040 * <p>Pre-implements serialization of RemoteInvocation objects and 041 * deserialization of RemoteInvocationResults objects. 042 * 043 * @author Juergen Hoeller 044 * @since 1.1 045 * @see #doExecuteRequest 046 */ 047public abstract class AbstractHttpInvokerRequestExecutor implements HttpInvokerRequestExecutor, BeanClassLoaderAware { 048 049 /** 050 * Default content type: "application/x-java-serialized-object" 051 */ 052 public static final String CONTENT_TYPE_SERIALIZED_OBJECT = "application/x-java-serialized-object"; 053 054 private static final int SERIALIZED_INVOCATION_BYTE_ARRAY_INITIAL_SIZE = 1024; 055 056 057 protected static final String HTTP_METHOD_POST = "POST"; 058 059 protected static final String HTTP_HEADER_ACCEPT_LANGUAGE = "Accept-Language"; 060 061 protected static final String HTTP_HEADER_ACCEPT_ENCODING = "Accept-Encoding"; 062 063 protected static final String HTTP_HEADER_CONTENT_ENCODING = "Content-Encoding"; 064 065 protected static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type"; 066 067 protected static final String HTTP_HEADER_CONTENT_LENGTH = "Content-Length"; 068 069 protected static final String ENCODING_GZIP = "gzip"; 070 071 072 protected final Log logger = LogFactory.getLog(getClass()); 073 074 private String contentType = CONTENT_TYPE_SERIALIZED_OBJECT; 075 076 private boolean acceptGzipEncoding = true; 077 078 private ClassLoader beanClassLoader; 079 080 081 /** 082 * Specify the content type to use for sending HTTP invoker requests. 083 * <p>Default is "application/x-java-serialized-object". 084 */ 085 public void setContentType(String contentType) { 086 Assert.notNull(contentType, "'contentType' must not be null"); 087 this.contentType = contentType; 088 } 089 090 /** 091 * Return the content type to use for sending HTTP invoker requests. 092 */ 093 public String getContentType() { 094 return this.contentType; 095 } 096 097 /** 098 * Set whether to accept GZIP encoding, that is, whether to 099 * send the HTTP "Accept-Encoding" header with "gzip" as value. 100 * <p>Default is "true". Turn this flag off if you do not want 101 * GZIP response compression even if enabled on the HTTP server. 102 */ 103 public void setAcceptGzipEncoding(boolean acceptGzipEncoding) { 104 this.acceptGzipEncoding = acceptGzipEncoding; 105 } 106 107 /** 108 * Return whether to accept GZIP encoding, that is, whether to 109 * send the HTTP "Accept-Encoding" header with "gzip" as value. 110 */ 111 public boolean isAcceptGzipEncoding() { 112 return this.acceptGzipEncoding; 113 } 114 115 @Override 116 public void setBeanClassLoader(ClassLoader classLoader) { 117 this.beanClassLoader = classLoader; 118 } 119 120 /** 121 * Return the bean ClassLoader that this executor is supposed to use. 122 */ 123 protected ClassLoader getBeanClassLoader() { 124 return this.beanClassLoader; 125 } 126 127 128 @Override 129 public final RemoteInvocationResult executeRequest( 130 HttpInvokerClientConfiguration config, RemoteInvocation invocation) throws Exception { 131 132 ByteArrayOutputStream baos = getByteArrayOutputStream(invocation); 133 if (logger.isDebugEnabled()) { 134 logger.debug("Sending HTTP invoker request for service at [" + config.getServiceUrl() + 135 "], with size " + baos.size()); 136 } 137 return doExecuteRequest(config, baos); 138 } 139 140 /** 141 * Serialize the given RemoteInvocation into a ByteArrayOutputStream. 142 * @param invocation the RemoteInvocation object 143 * @return a ByteArrayOutputStream with the serialized RemoteInvocation 144 * @throws IOException if thrown by I/O methods 145 */ 146 protected ByteArrayOutputStream getByteArrayOutputStream(RemoteInvocation invocation) throws IOException { 147 ByteArrayOutputStream baos = new ByteArrayOutputStream(SERIALIZED_INVOCATION_BYTE_ARRAY_INITIAL_SIZE); 148 writeRemoteInvocation(invocation, baos); 149 return baos; 150 } 151 152 /** 153 * Serialize the given RemoteInvocation to the given OutputStream. 154 * <p>The default implementation gives {@code decorateOutputStream} a chance 155 * to decorate the stream first (for example, for custom encryption or compression). 156 * Creates an {@code ObjectOutputStream} for the final stream and calls 157 * {@code doWriteRemoteInvocation} to actually write the object. 158 * <p>Can be overridden for custom serialization of the invocation. 159 * @param invocation the RemoteInvocation object 160 * @param os the OutputStream to write to 161 * @throws IOException if thrown by I/O methods 162 * @see #decorateOutputStream 163 * @see #doWriteRemoteInvocation 164 */ 165 protected void writeRemoteInvocation(RemoteInvocation invocation, OutputStream os) throws IOException { 166 ObjectOutputStream oos = new ObjectOutputStream(decorateOutputStream(os)); 167 try { 168 doWriteRemoteInvocation(invocation, oos); 169 } 170 finally { 171 oos.close(); 172 } 173 } 174 175 /** 176 * Return the OutputStream to use for writing remote invocations, 177 * potentially decorating the given original OutputStream. 178 * <p>The default implementation returns the given stream as-is. 179 * Can be overridden, for example, for custom encryption or compression. 180 * @param os the original OutputStream 181 * @return the potentially decorated OutputStream 182 */ 183 protected OutputStream decorateOutputStream(OutputStream os) throws IOException { 184 return os; 185 } 186 187 /** 188 * Perform the actual writing of the given invocation object to the 189 * given ObjectOutputStream. 190 * <p>The default implementation simply calls {@code writeObject}. 191 * Can be overridden for serialization of a custom wrapper object rather 192 * than the plain invocation, for example an encryption-aware holder. 193 * @param invocation the RemoteInvocation object 194 * @param oos the ObjectOutputStream to write to 195 * @throws IOException if thrown by I/O methods 196 * @see java.io.ObjectOutputStream#writeObject 197 */ 198 protected void doWriteRemoteInvocation(RemoteInvocation invocation, ObjectOutputStream oos) throws IOException { 199 oos.writeObject(invocation); 200 } 201 202 203 /** 204 * Execute a request to send the given serialized remote invocation. 205 * <p>Implementations will usually call {@code readRemoteInvocationResult} 206 * to deserialize a returned RemoteInvocationResult object. 207 * @param config the HTTP invoker configuration that specifies the 208 * target service 209 * @param baos the ByteArrayOutputStream that contains the serialized 210 * RemoteInvocation object 211 * @return the RemoteInvocationResult object 212 * @throws IOException if thrown by I/O operations 213 * @throws ClassNotFoundException if thrown during deserialization 214 * @throws Exception in case of general errors 215 * @see #readRemoteInvocationResult(java.io.InputStream, String) 216 */ 217 protected abstract RemoteInvocationResult doExecuteRequest( 218 HttpInvokerClientConfiguration config, ByteArrayOutputStream baos) 219 throws Exception; 220 221 /** 222 * Deserialize a RemoteInvocationResult object from the given InputStream. 223 * <p>Gives {@code decorateInputStream} a chance to decorate the stream 224 * first (for example, for custom encryption or compression). Creates an 225 * {@code ObjectInputStream} via {@code createObjectInputStream} and 226 * calls {@code doReadRemoteInvocationResult} to actually read the object. 227 * <p>Can be overridden for custom serialization of the invocation. 228 * @param is the InputStream to read from 229 * @param codebaseUrl the codebase URL to load classes from if not found locally 230 * @return the RemoteInvocationResult object 231 * @throws IOException if thrown by I/O methods 232 * @throws ClassNotFoundException if thrown during deserialization 233 * @see #decorateInputStream 234 * @see #createObjectInputStream 235 * @see #doReadRemoteInvocationResult 236 */ 237 protected RemoteInvocationResult readRemoteInvocationResult(InputStream is, String codebaseUrl) 238 throws IOException, ClassNotFoundException { 239 240 ObjectInputStream ois = createObjectInputStream(decorateInputStream(is), codebaseUrl); 241 try { 242 return doReadRemoteInvocationResult(ois); 243 } 244 finally { 245 ois.close(); 246 } 247 } 248 249 /** 250 * Return the InputStream to use for reading remote invocation results, 251 * potentially decorating the given original InputStream. 252 * <p>The default implementation returns the given stream as-is. 253 * Can be overridden, for example, for custom encryption or compression. 254 * @param is the original InputStream 255 * @return the potentially decorated InputStream 256 */ 257 protected InputStream decorateInputStream(InputStream is) throws IOException { 258 return is; 259 } 260 261 /** 262 * Create an ObjectInputStream for the given InputStream and codebase. 263 * The default implementation creates a CodebaseAwareObjectInputStream. 264 * @param is the InputStream to read from 265 * @param codebaseUrl the codebase URL to load classes from if not found locally 266 * (can be {@code null}) 267 * @return the new ObjectInputStream instance to use 268 * @throws IOException if creation of the ObjectInputStream failed 269 * @see org.springframework.remoting.rmi.CodebaseAwareObjectInputStream 270 */ 271 protected ObjectInputStream createObjectInputStream(InputStream is, String codebaseUrl) throws IOException { 272 return new CodebaseAwareObjectInputStream(is, getBeanClassLoader(), codebaseUrl); 273 } 274 275 /** 276 * Perform the actual reading of an invocation object from the 277 * given ObjectInputStream. 278 * <p>The default implementation simply calls {@code readObject}. 279 * Can be overridden for deserialization of a custom wrapper object rather 280 * than the plain invocation, for example an encryption-aware holder. 281 * @param ois the ObjectInputStream to read from 282 * @return the RemoteInvocationResult object 283 * @throws IOException if thrown by I/O methods 284 * @throws ClassNotFoundException if the class name of a serialized object 285 * couldn't get resolved 286 * @see java.io.ObjectOutputStream#writeObject 287 */ 288 protected RemoteInvocationResult doReadRemoteInvocationResult(ObjectInputStream ois) 289 throws IOException, ClassNotFoundException { 290 291 Object obj = ois.readObject(); 292 if (!(obj instanceof RemoteInvocationResult)) { 293 throw new RemoteException("Deserialized object needs to be assignable to type [" + 294 RemoteInvocationResult.class.getName() + "]: " + ClassUtils.getDescriptiveType(obj)); 295 } 296 return (RemoteInvocationResult) obj; 297 } 298 299}