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