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.IOException; 020import java.io.InvalidClassException; 021import java.net.ConnectException; 022 023import org.aopalliance.intercept.MethodInterceptor; 024import org.aopalliance.intercept.MethodInvocation; 025 026import org.springframework.aop.support.AopUtils; 027import org.springframework.lang.Nullable; 028import org.springframework.remoting.RemoteAccessException; 029import org.springframework.remoting.RemoteConnectFailureException; 030import org.springframework.remoting.RemoteInvocationFailureException; 031import org.springframework.remoting.support.RemoteInvocation; 032import org.springframework.remoting.support.RemoteInvocationBasedAccessor; 033import org.springframework.remoting.support.RemoteInvocationResult; 034 035/** 036 * {@link org.aopalliance.intercept.MethodInterceptor} for accessing an 037 * HTTP invoker service. The service URL must be an HTTP URL exposing 038 * an HTTP invoker service. 039 * 040 * <p>Serializes remote invocation objects and deserializes remote invocation 041 * result objects. Uses Java serialization just like RMI, but provides the 042 * same ease of setup as Caucho's HTTP-based Hessian protocol. 043 * 044 * <P>HTTP invoker is a very extensible and customizable protocol. 045 * It supports the RemoteInvocationFactory mechanism, like RMI invoker, 046 * allowing to include additional invocation attributes (for example, 047 * a security context). Furthermore, it allows to customize request 048 * execution via the {@link HttpInvokerRequestExecutor} strategy. 049 * 050 * <p>Can use the JDK's {@link java.rmi.server.RMIClassLoader} to load classes 051 * from a given {@link #setCodebaseUrl codebase}, performing on-demand dynamic 052 * code download from a remote location. The codebase can consist of multiple 053 * URLs, separated by spaces. Note that RMIClassLoader requires a SecurityManager 054 * to be set, analogous to when using dynamic class download with standard RMI! 055 * (See the RMI documentation for details.) 056 * 057 * <p><b>WARNING: Be aware of vulnerabilities due to unsafe Java deserialization: 058 * Manipulated input streams could lead to unwanted code execution on the server 059 * during the deserialization step. As a consequence, do not expose HTTP invoker 060 * endpoints to untrusted clients but rather just between your own services.</b> 061 * In general, we strongly recommend any other message format (e.g. JSON) instead. 062 * 063 * @author Juergen Hoeller 064 * @since 1.1 065 * @see #setServiceUrl 066 * @see #setCodebaseUrl 067 * @see #setRemoteInvocationFactory 068 * @see #setHttpInvokerRequestExecutor 069 * @see HttpInvokerServiceExporter 070 * @see HttpInvokerProxyFactoryBean 071 * @see java.rmi.server.RMIClassLoader 072 */ 073public class HttpInvokerClientInterceptor extends RemoteInvocationBasedAccessor 074 implements MethodInterceptor, HttpInvokerClientConfiguration { 075 076 @Nullable 077 private String codebaseUrl; 078 079 @Nullable 080 private HttpInvokerRequestExecutor httpInvokerRequestExecutor; 081 082 083 /** 084 * Set the codebase URL to download classes from if not found locally. 085 * Can consists of multiple URLs, separated by spaces. 086 * <p>Follows RMI's codebase conventions for dynamic class download. 087 * In contrast to RMI, where the server determines the URL for class download 088 * (via the "java.rmi.server.codebase" system property), it's the client 089 * that determines the codebase URL here. The server will usually be the 090 * same as for the service URL, just pointing to a different path there. 091 * @see #setServiceUrl 092 * @see org.springframework.remoting.rmi.CodebaseAwareObjectInputStream 093 * @see java.rmi.server.RMIClassLoader 094 */ 095 public void setCodebaseUrl(@Nullable String codebaseUrl) { 096 this.codebaseUrl = codebaseUrl; 097 } 098 099 /** 100 * Return the codebase URL to download classes from if not found locally. 101 */ 102 @Override 103 @Nullable 104 public String getCodebaseUrl() { 105 return this.codebaseUrl; 106 } 107 108 /** 109 * Set the HttpInvokerRequestExecutor implementation to use for executing 110 * remote invocations. 111 * <p>Default is {@link SimpleHttpInvokerRequestExecutor}. Alternatively, 112 * consider using {@link HttpComponentsHttpInvokerRequestExecutor} for more 113 * sophisticated needs. 114 * @see SimpleHttpInvokerRequestExecutor 115 * @see HttpComponentsHttpInvokerRequestExecutor 116 */ 117 public void setHttpInvokerRequestExecutor(HttpInvokerRequestExecutor httpInvokerRequestExecutor) { 118 this.httpInvokerRequestExecutor = httpInvokerRequestExecutor; 119 } 120 121 /** 122 * Return the HttpInvokerRequestExecutor used by this remote accessor. 123 * <p>Creates a default SimpleHttpInvokerRequestExecutor if no executor 124 * has been initialized already. 125 */ 126 public HttpInvokerRequestExecutor getHttpInvokerRequestExecutor() { 127 if (this.httpInvokerRequestExecutor == null) { 128 SimpleHttpInvokerRequestExecutor executor = new SimpleHttpInvokerRequestExecutor(); 129 executor.setBeanClassLoader(getBeanClassLoader()); 130 this.httpInvokerRequestExecutor = executor; 131 } 132 return this.httpInvokerRequestExecutor; 133 } 134 135 @Override 136 public void afterPropertiesSet() { 137 super.afterPropertiesSet(); 138 139 // Eagerly initialize the default HttpInvokerRequestExecutor, if needed. 140 getHttpInvokerRequestExecutor(); 141 } 142 143 144 @Override 145 public Object invoke(MethodInvocation methodInvocation) throws Throwable { 146 if (AopUtils.isToStringMethod(methodInvocation.getMethod())) { 147 return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]"; 148 } 149 150 RemoteInvocation invocation = createRemoteInvocation(methodInvocation); 151 RemoteInvocationResult result; 152 153 try { 154 result = executeRequest(invocation, methodInvocation); 155 } 156 catch (Throwable ex) { 157 RemoteAccessException rae = convertHttpInvokerAccessException(ex); 158 throw (rae != null ? rae : ex); 159 } 160 161 try { 162 return recreateRemoteInvocationResult(result); 163 } 164 catch (Throwable ex) { 165 if (result.hasInvocationTargetException()) { 166 throw ex; 167 } 168 else { 169 throw new RemoteInvocationFailureException("Invocation of method [" + methodInvocation.getMethod() + 170 "] failed in HTTP invoker remote service at [" + getServiceUrl() + "]", ex); 171 } 172 } 173 } 174 175 /** 176 * Execute the given remote invocation via the {@link HttpInvokerRequestExecutor}. 177 * <p>This implementation delegates to {@link #executeRequest(RemoteInvocation)}. 178 * Can be overridden to react to the specific original MethodInvocation. 179 * @param invocation the RemoteInvocation to execute 180 * @param originalInvocation the original MethodInvocation (can e.g. be cast 181 * to the ProxyMethodInvocation interface for accessing user attributes) 182 * @return the RemoteInvocationResult object 183 * @throws Exception in case of errors 184 */ 185 protected RemoteInvocationResult executeRequest( 186 RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception { 187 188 return executeRequest(invocation); 189 } 190 191 /** 192 * Execute the given remote invocation via the {@link HttpInvokerRequestExecutor}. 193 * <p>Can be overridden in subclasses to pass a different configuration object 194 * to the executor. Alternatively, add further configuration properties in a 195 * subclass of this accessor: By default, the accessor passed itself as 196 * configuration object to the executor. 197 * @param invocation the RemoteInvocation to execute 198 * @return the RemoteInvocationResult object 199 * @throws IOException if thrown by I/O operations 200 * @throws ClassNotFoundException if thrown during deserialization 201 * @throws Exception in case of general errors 202 * @see #getHttpInvokerRequestExecutor 203 * @see HttpInvokerClientConfiguration 204 */ 205 protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws Exception { 206 return getHttpInvokerRequestExecutor().executeRequest(this, invocation); 207 } 208 209 /** 210 * Convert the given HTTP invoker access exception to an appropriate 211 * Spring {@link RemoteAccessException}. 212 * @param ex the exception to convert 213 * @return the RemoteAccessException to throw, or {@code null} to have the 214 * original exception propagated to the caller 215 */ 216 @Nullable 217 protected RemoteAccessException convertHttpInvokerAccessException(Throwable ex) { 218 if (ex instanceof ConnectException) { 219 return new RemoteConnectFailureException( 220 "Could not connect to HTTP invoker remote service at [" + getServiceUrl() + "]", ex); 221 } 222 223 if (ex instanceof ClassNotFoundException || ex instanceof NoClassDefFoundError || 224 ex instanceof InvalidClassException) { 225 return new RemoteAccessException( 226 "Could not deserialize result from HTTP invoker remote service [" + getServiceUrl() + "]", ex); 227 } 228 229 if (ex instanceof Exception) { 230 return new RemoteAccessException( 231 "Could not access HTTP invoker remote service at [" + getServiceUrl() + "]", ex); 232 } 233 234 // For any other Throwable, e.g. OutOfMemoryError: let it get propagated as-is. 235 return null; 236 } 237 238}