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