最近在开发一个RPC服务,在多种RPC框架之间选来选取,最终还是选择了hessian,整体上来说,hessian不仅简单而且优秀,还符合“微服务”的轻量级服务治理的理念;这个RPC框架优秀到简直没有升级和扩展的必要... 不过在实际开发中,遇到了一个小小的问题,就是如何通过hessian协议(框架)来发送一些“附属信息”,比如token等;这些附属信息,内容较小,但是可能条目个数较多,如果都封装成JAVA对象通过API传送,确实引入一些扩展性的问题。既然hessian底层基于HTTP协议,这些附属信息能否通过Header传递呢? 经过思考和验证,不仅可以,而且这也是最佳的策略;此后Client端如果需要额外传递更多的附属信息,则不需要升级API Client,透明易用。同时通过HEADER传送一些信息,这对WEB Proxy层也是良好的。
设计要求:
1、对于一个附属信息,比如TOKEN、备注等附属信息,不能通过hessian API传送。
2、附属信息可能很多,不过需要支持动态增加,增加时不应该修改hessian API,以便给Client带来兼容性问题。
3、性能问题需要兼顾。
4、附属信息通过HTTP Headers发送。
经过研究Hessian源码,发现Hessian请求默认会在header中添加一些标识信息,不过addHeader的操作是“关闭的”,不能通过简单的Spring配置或者Hessian API额外的增加其他的header;为了支持此特性,我们需要扩展Hessian API,本实例基于Spring容器。
1、我们希望Headers可以通过Spring配置方式制定。
2、我们希望可以在runtime期间,Hessian请求发送时开发者可以自定headers并通过Hessian协议发送。
3、Hessian Remote Service应该可以解析出header,并能够在Service内部获得header的内容,以便使用。
4、请求响应结束后,这些headers应该被Clear,即无状态。
设计思路:
1)那些可以通过Spring配置指定的headers,我们可以通过扩展Hessian FactoryBean来实现,将配置中指定的header(通常为全局headers)保存在Spring Bean属性中。
2)那些需要在runtime期间动态添加的headers,为了避免耦合,我们可以使用ThreadLocal方式,将它们保存在Context中,并在Hessian请求实际发送之间,扩展其addHeader方法,并将它们添加在HTTP 请求中。
3)Hessian Remote Service,通常为HTTP Servlet实例,那么我们可以基于Fitler的方式解析这些headers,这也要求Hessian Headers需要遵循一定的规则,比如header均已“x-rpc-hessian”开头等。
5)对于Spring容器中的remote service,我们也可以扩展相应的FactoryBean,来解析这些header,并放置在ThreadLocal中,此后Service执行过程中,就可以获取这些headers内容。
一、HContext.java
一个基于ThreadLocal实现的Context类,用于保存runtime期间开发者动态添加的headers。
import java.net.URLDecoder; import java.net.URLEncoder; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * Created by liuguanqing on 17/4/12. * 如果header中,有中文,需要进行URLEncoder后才能发送 */ public class HContext { public static final String KEY_PREFIX = "x-rpc-"; public static final String UTF8 = "utf-8"; public static final String SOURCE_HOST = "source_host"; public static final String REQUEST_TYPE = "request_type"; private static final ThreadLocal<Map<String,String>> context = new InheritableThreadLocal<>(); public static void init() { Map<String, String> model = context.get(); if(model == null) { context.set(new HashMap<>()); } } public static void put(String key,String value) { if(value == null) { return; } Map<String,String> model = context.get(); if(model == null) { init(); } if(!key.startsWith(KEY_PREFIX)) { key = KEY_PREFIX + key; } context.get().put(key,value); } public static void putAll(Map<String,String> kvs) { if(kvs == null || kvs.isEmpty()) { return; } for(Map.Entry<String,String> entry : kvs.entrySet()) { String value = entry.getValue(); if(value == null) { continue; } put(entry.getKey(),value); } } public static String get(String key) { key = KEY_PREFIX + key; Map<String,String> model = context.get(); if(model == null) { return null; } return model.get(key); } public static Map<String,String> getAll() { Map<String,String> model = context.get(); if(model == null) { return Collections.EMPTY_MAP; } return model; } public static void clear() { context.remove(); } }
需要注意,如果headers中包含中文,需要进行UrlEncoder之后才能发送,对应在Service端则需要UrlDecoder。
二、HessianProxy扩展类
大家都知道,Hessian客户端必须创建一个HessianProxy代理类,才能进行RPC调用。HessianProxy是通过JAVA动态代理方式创建,其代理了Serivce API的接口。HessianProxy负责进行实际的RPC请求和响应处理,它在发送请求时总会调用内部的addHeader方法,那么我们即可围绕addHeader方法开展。
import com.caucho.hessian.client.HessianConnection; import com.caucho.hessian.client.HessianProxy; import com.caucho.hessian.client.HessianProxyFactory; import java.lang.reflect.Method; import java.net.URL; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; /** * Created by liuguanqing on 17/4/12. * */ public class HessianWithContextProxy extends HessianProxy { protected Map<String,String> globalHeaders = new HashMap<>(); public HessianWithContextProxy(URL url, HessianProxyFactory factory) { super(url, factory); HContext.init(); } public HessianWithContextProxy(URL url, HessianProxyFactory factory, Class type) { super(url, factory, type); HContext.init(); } public HessianWithContextProxy(URL url, HessianProxyFactory factory, Class type,Map<String,String> headers) { super(url, factory, type); HContext.init(); globalHeaders = headers; } protected void addRequestHeaders(HessianConnection conn) { super.addRequestHeaders(conn); try { HContext.putAll(globalHeaders);//global headers cant be replaced! Map<String, String> context = HContext.getAll(); for (Map.Entry<String, String> entry : context.entrySet()) { try { String value = entry.getValue(); if (value == null) { continue; } conn.addHeader(entry.getKey(), URLEncoder.encode(value, HContext.UTF8)); } catch (Exception e) { // } } } finally { HContext.clear(); //after send,we clear at once. //must clear context here. } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return super.invoke(proxy,method,args); } }
对于那些global级别的headers,即通过Spring配置文件声明的、所有请求都公用的headers,我们应该在保存在HessianProxy实例的内部;对于那些runtime期间动态增加的headers,我们需要从HContext中获取,并将它们均通过addHeaders方法添加到connection中。考虑到ThreadLocal的容器的生命周期管理,我们必须在addHeaders方法调用结束后,进行clear。
在考虑HContext.clear的时机时,本人曾经引入过一些问题。起初,本人在invoke方法返回之前调用clear,事实上经过测试发现,这是错误的。需要注意,invoke方法的执行时机时“调用API的任何方法都会执行”(包括toString、getClass等等),所以Client请求在发送之前,可能多次执行invoke,那些不是API方法的执行,会导致HContext数据被清除。所以,我们应该在临近请求发送的地方进行HContext操作。
三、HessianProxyFactoryBean扩展
HessianProxy是有HessianProxyFactory创建的,我们通常可以在此FactoryBean中指定serviceUrl等等一些Hessian Client的配置信息,当然我们可以扩展它,并支持配置一些global级别的headers,这些headers,通常是一些常量,此Service Client的所有请求都可以通用;比如Token等。
import com.caucho.hessian.client.HessianProxyFactory; import com.caucho.hessian.io.HessianRemoteObject; import java.lang.reflect.Proxy; import java.net.URL; import java.util.HashMap; import java.util.Map; /** * Created by liuguanqing on 17/4/12. */ public class HessianWithContextProxyFactory extends HessianProxyFactory { private Map<String,String> globalHeaders = new HashMap<>(); public HessianWithContextProxyFactory() { super(); } public void setGlobalHeaders(Map<String, String> globalHeaders) { this.globalHeaders = globalHeaders; } public Map<String, String> getGlobalHeaders() { return globalHeaders; } @Override public Object create(Class api, URL url, ClassLoader loader) { if (api == null) throw new NullPointerException("api must not be null for HessianProxyFactory.create()"); HessianWithContextProxy handler = new HessianWithContextProxy(url, this, api,globalHeaders); return Proxy.newProxyInstance(loader, new Class[]{api, HessianRemoteObject.class}, handler); } }
这个扩展类比较简单,只需要注意,在创建HessianProxy实例时,将配置的global headers作为参数传递过去。因为面向开发者或者Spring框架时,只有HessianProxyFactory是可见的,所以这些参数只能通过FactoryBean配置然后传递给HessianProxy。
四、基于Spring框架的ProxyFactoryBean扩展
Spring支持Hessian框架,它基于HessianProxyFactoryBean实现,这个类名与Hessian原始的“HessianProxyFactory”比较类似,不过它是Spring容器的工厂bean,用于创建单例模式的HessianProxyFactory实例。注意此类是面向Hessian Client端!
import java.net.InetAddress; import java.net.NetworkInterface; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; /** * Created by liuguanqing on 17/4/12. */ public class HessianWithContextProxyFactoryBean extends HessianProxyFactoryBean { protected HessianWithContextProxyFactory proxyFactory = new HessianWithContextProxyFactory(); protected Map<String,String> headers = new HashMap<>(); public void setHeaders(Map<String, String> headers) { this.headers = headers; } private String localIp; public HessianWithContextProxyFactoryBean() { super(); super.setProxyFactory(proxyFactory);//强制修改 } @Override public void afterPropertiesSet() { super.afterPropertiesSet(); localIp = getLocalIp();//本地IP地址 headers.put(HContext.SOURCE_HOST,localIp); proxyFactory.getGlobalHeaders().putAll(headers);//append } @Override public Object invoke(MethodInvocation invocation) throws Throwable { //all the methods of hessian instances will be invoked here, //so we cant use threadLocal there. return super.invoke(invocation); } public String getLocalIp() { try { //一个主机有多个网络接口 Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces(); while (netInterfaces.hasMoreElements()) { NetworkInterface netInterface = netInterfaces.nextElement(); Enumeration<InetAddress> addresses = netInterface.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress address = addresses.nextElement(); if (address.isSiteLocalAddress() && !address.isLoopbackAddress()) { return address.getHostAddress(); } } } }catch (Exception e) { // } return null; } }
没有特别之处,只是我们默认添加了一个全局header,表示Client端的本机IP地址,主要用于Remote Service跟踪请求的来源。
五、Remote Service端
import org.springframework.remoting.caucho.HessianServiceExporter; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLDecoder; import java.util.Enumeration; /** * Created by liuguanqing on 17/4/12. * */ public class HessianWithContextServiceExporter extends HessianServiceExporter { @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //处理请求 try { Enumeration<String> headers = request.getHeaderNames(); while (headers.hasMoreElements()) { String key = headers.nextElement(); if(key.startsWith(HContext.KEY_PREFIX)) { try { String value = request.getHeader(key); if(value != null) { HContext.put(key, URLDecoder.decode(value,HContext.UTF8)); } } catch (Exception e) { // } } } HContext.put(HContext.REQUEST_TYPE,"SDK"); super.handleRequest(request,response); } finally { HContext.clear(); } } }
HessianServiceExporter是Spring提供的机制,即可以将Spring Bean暴露并与Servlet容器结合(Spring-web),它只有一个主要的方法,用于处理Client发送的请求,那么我们可以在此方法中解析Client发送的headers,并保存在HContext中。
六、使用方式
1、Client端配置
<bean id="remotePortalService" class="com.demo.hessian.spring.HessianWithContextProxyFactoryBean"> <property name="serviceUrl" value="${portal.service.url}" /> <property name="serviceInterface" value="com.demo.service.PortalService" /> <property name="overloadEnabled" value="true"/> <property name="connectTimeout" value="3000"/> <property name="hessian2" value="true"/> <property name="readTimeout" value="3000"/> <property name="headers"> <map> <entry key="project" value="${portal.service.project}"/> <entry key="token" value="${.portal.service.token}" /> </map> </property> </bean>
2、Client端JAVA代码样例
HContext.put("comment","这是Hessian服务"); HContext.put("operator","zhangsan"); remotePortalService.send(target); //不需要对HContext进行clear,HessianProxy会自动执行。
3、Remote Service端配置(web.xml)
<servlet> <servlet-name>hessianService</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-hessian.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>hessianService</servlet-name> <url-pattern>/rpc/*</url-pattern> </servlet-mapping>
spring-hessian.xml配置
<bean name="/portal" class="com.vipkid.utils.hessian.spring.HessianWithContextServiceExporter"> <property name="service" ref="portalService"/> <property name="serviceInterface" value="com.demo.service.PortalService"/> </bean>
你可能还需要在其他配置文件中声明“portalService”,这是一个Spring Bean。此外,你的Spring Controller的URI路径也需要以“/rpc”开头,即与web.xml配置保持一致。
4、Remote Service端JAVA示例
public Object send(Object target) { String clientId = HContext.get("source_host"); //HContext已经在Exportor中进行了数据准备,所以可以直接使用,也不需要clear。 }
相关推荐
总结来说,"dubbo-hessian协议http请求demo"这个示例主要展示了如何在Java环境中利用Dubbo的Hessian协议进行服务间的HTTP通信。通过理解Hessian协议的特性,我们可以实现高效、简洁的远程方法调用,提升分布式系统的...
当我们谈论“Hessian与Spring整合”时,通常是指将Hessian作为服务通信的机制,结合Spring框架来构建分布式服务系统。这种整合有以下几个关键点: 1. **服务提供者(Service Provider)**:首先,我们需要在服务端...
当Hessian与Spring框架结合时,步骤稍有不同: 1. **依赖和接口**:同样需要引入Hessian库,并定义服务接口。但此时,接口的实现可以是普通的Plain Old Java Objects (POJOs)。 2. **Spring配置**:在服务端的...
在将Hessian与Spring进行整合时,我们需要确保引入了正确的jar包。这些jar包通常包括以下几个核心组件: 1. **Hessian库**:这是实现Hessian RPC的基础,包含了序列化和反序列化的类以及远程调用的相关接口。主要的...
当我们将Hessian与Spring进行整合时,主要目标是利用Spring的依赖注入(Dependency Injection, DI)和组件管理能力来简化Hessian服务的创建和管理。以下是一些关键知识点: 1. **Spring核心模块**(spring-core-...
**Hessian:深入理解与应用** Hessian是一种二进制Web服务协议,它由Caucho Technology公司开发,主要用于提供轻量级、高效的远程方法调用(Remote Method Invocation,RMI)服务。Hessian的目标是简化分布式系统...
3. **Python中的Hessian**:Python也有对应的Hessian库,如`pyhessian`,它实现了Hessian协议,使得Python应用能够与Java Hessian服务进行通信。同样,服务端可以通过定义Python函数并包装为Hessian服务,客户端则...
比如,如果项目依赖于旧的API或者需要与旧系统兼容,那么Hessian3.1.6可能是合适的选择。如果追求更高的性能和安全性,那么Hessian4.0.7可能更优。在实际应用中,理解每个版本的特点和改进,能够帮助我们做出明智的...
Hessian 协议是一种轻量级的 remoting on http 工具,使用简单的方法提供了 RMI 的功能。采用的是二进制 RPC 协议,所以它很适合于发送二进制数据。Hessian 主要用作面向对象的消息通信。 Hessian 协议报文格式可以...
与基于XML的SOAP相比,Hessian的性能更高,因为XML解析和生成的开销较大。Hessian支持两种主要的调用类型:HTTP GET和HTTP POST,以及HTTP之外的TCP/IP连接。 **Spring与Hessian的集成** 在Spring中集成Hessian,...
Hessian是一种二进制Web服务协议,由Caucho Technology公司开发,主要用于提供轻量级、高效的远程方法调用(Remote Method Invocation, RMI)机制。它结合了HTTP协议的可扩展性和Java序列化机制的易用性,使得在...
在图像处理领域,多尺度Hessian滤波器是一种高级的图像增强技术,它主要用于检测图像中的线性结构,特别是对于微弱或者噪声较大的图像特征有很好的识别能力。这个技术是基于数学形态学的Hessian矩阵理论,由V.S. ...
文件"基于Hessian矩阵增强的心血管分割"可能包含了实现这一方法的详细步骤、算法描述、实验结果以及与其他方法的比较。通过这样的技术,可以精确地分割出冠状动脉血管,帮助医生更准确地诊断和治疗心血管疾病,从而...
与常见的基于XML的RPC协议相比,Hessian采用二进制格式,数据传输更紧凑,解析速度更快,减少了网络带宽的消耗。 Hessian的核心是其序列化和反序列化机制。序列化是将Java对象转换为二进制流的过程,而反序列化则是...
- Hessian服务的URL需要与服务器上的部署路径匹配。 - 确保服务端和客户端的环境一致,包括JDK版本、Hessian库版本等。 - 考虑安全性问题,Hessian默认不加密传输,可以通过HTTPS或自定义Filter增强安全性。 通过...
Hessian、HttpInvoker、XFire和Axis是四种常见的远程调用框架,它们各自有不同的特性和应用场景。 Hessian是一种轻量级的二进制RPC协议,它通过HTTP进行传输,减少了网络开销,提高了服务调用效率。Hessian提供了...
- Java客户端会通过Hessian库与服务器端建立连接,调用服务端暴露的方法。 - 客户端代码需要知道服务器的URL以及要调用的服务接口。Hessian库会自动处理序列化和反序列化,使得客户端可以像调用本地方法一样调用...
5. **安全特性**:尽管Hessian协议本身不包含安全机制,但它可以与HTTPS结合使用,确保数据传输的安全性。 学习和研究Hessian源代码,可以帮助我们理解以下内容: - 如何实现高效的二进制序列化和反序列化算法。 -...
与传统的基于XML的Web服务相比,Hessian在处理复杂数据结构时更加高效。此外,Hessian还支持类型安全的远程方法调用,这意味着客户端和服务端可以自动匹配方法签名,减少编码错误。 现在,让我们通过一个简单的Java...
Hessian是一种基于二进制协议的服务,它允许Java应用程序通过HTTP进行远程方法调用(RMI)。这个协议设计的目标是提供轻量级、高效的RPC(Remote Procedure Call)解决方案,尤其适用于内部网络或需要低延迟通信的...