`
QING____
  • 浏览: 2249829 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Hessian与HTTP Header

 
阅读更多

    最近在开发一个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。
}

 

  • 大小: 30.4 KB
分享到:
评论

相关推荐

    dubbo-hessian协议http请求demo(java)

    总结来说,"dubbo-hessian协议http请求demo"这个示例主要展示了如何在Java环境中利用Dubbo的Hessian协议进行服务间的HTTP通信。通过理解Hessian协议的特性,我们可以实现高效、简洁的远程方法调用,提升分布式系统的...

    Hessian与spring整合

    当我们谈论“Hessian与Spring整合”时,通常是指将Hessian作为服务通信的机制,结合Spring框架来构建分布式服务系统。这种整合有以下几个关键点: 1. **服务提供者(Service Provider)**:首先,我们需要在服务端...

    Hessian与Spring整合需要jar包

    在将Hessian与Spring进行整合时,我们需要确保引入了正确的jar包。这些jar包通常包括以下几个核心组件: 1. **Hessian库**:这是实现Hessian RPC的基础,包含了序列化和反序列化的类以及远程调用的相关接口。主要的...

    hessian与spring整合的jar包

    当我们将Hessian与Spring进行整合时,主要目标是利用Spring的依赖注入(Dependency Injection, DI)和组件管理能力来简化Hessian服务的创建和管理。以下是一些关键知识点: 1. **Spring核心模块**(spring-core-...

    Hessian

    **Hessian:深入理解与应用** Hessian是一种二进制Web服务协议,它由Caucho Technology公司开发,主要用于提供轻量级、高效的远程方法调用(Remote Method Invocation,RMI)服务。Hessian的目标是简化分布式系统...

    hessian案例,hessian案例

    3. **Python中的Hessian**:Python也有对应的Hessian库,如`pyhessian`,它实现了Hessian协议,使得Python应用能够与Java Hessian服务进行通信。同样,服务端可以通过定义Python函数并包装为Hessian服务,客户端则...

    Hessian多个版本打包下载

    比如,如果项目依赖于旧的API或者需要与旧系统兼容,那么Hessian3.1.6可能是合适的选择。如果追求更高的性能和安全性,那么Hessian4.0.7可能更优。在实际应用中,理解每个版本的特点和改进,能够帮助我们做出明智的...

    Hessian协议格式

    Hessian 协议是一种轻量级的 remoting on http 工具,使用简单的方法提供了 RMI 的功能。采用的是二进制 RPC 协议,所以它很适合于发送二进制数据。Hessian 主要用作面向对象的消息通信。 Hessian 协议报文格式可以...

    Hessian 使用小结

    当Hessian与Spring框架结合时,步骤稍有不同: 1. **依赖和接口**:同样需要引入Hessian库,并定义服务接口。但此时,接口的实现可以是普通的Plain Old Java Objects (POJOs)。 2. **Spring配置**:在服务端的...

    Spring中集成Hessian的问题

    与基于XML的SOAP相比,Hessian的性能更高,因为XML解析和生成的开销较大。Hessian支持两种主要的调用类型:HTTP GET和HTTP POST,以及HTTP之外的TCP/IP连接。 **Spring与Hessian的集成** 在Spring中集成Hessian,...

    hessian

    Hessian是一种二进制Web服务协议,由Caucho Technology公司开发,主要用于提供轻量级、高效的远程方法调用(Remote Method Invocation, RMI)机制。它结合了HTTP协议的可扩展性和Java序列化机制的易用性,使得在...

    多尺度hessian滤波器图像增强

    在图像处理领域,多尺度Hessian滤波器是一种高级的图像增强技术,它主要用于检测图像中的线性结构,特别是对于微弱或者噪声较大的图像特征有很好的识别能力。这个技术是基于数学形态学的Hessian矩阵理论,由V.S. ...

    基于Hessian矩阵增强的心血管分割_hessian_hessian血管_hessian血管分割_血管分割_Hessian矩阵

    文件"基于Hessian矩阵增强的心血管分割"可能包含了实现这一方法的详细步骤、算法描述、实验结果以及与其他方法的比较。通过这样的技术,可以精确地分割出冠状动脉血管,帮助医生更准确地诊断和治疗心血管疾病,从而...

    Hessian应用

    与常见的基于XML的RPC协议相比,Hessian采用二进制格式,数据传输更紧凑,解析速度更快,减少了网络带宽的消耗。 Hessian的核心是其序列化和反序列化机制。序列化是将Java对象转换为二进制流的过程,而反序列化则是...

    Hessian的Spring配置

    - Hessian服务的URL需要与服务器上的部署路径匹配。 - 确保服务端和客户端的环境一致,包括JDK版本、Hessian库版本等。 - 考虑安全性问题,Hessian默认不加密传输,可以通过HTTPS或自定义Filter增强安全性。 通过...

    Hessian 、 HttpInvoker 、 XFire 、 Axis

    Hessian、HttpInvoker、XFire和Axis是四种常见的远程调用框架,它们各自有不同的特性和应用场景。 Hessian是一种轻量级的二进制RPC协议,它通过HTTP进行传输,减少了网络开销,提高了服务调用效率。Hessian提供了...

    hessian php与java通讯demo源码

    - Java客户端会通过Hessian库与服务器端建立连接,调用服务端暴露的方法。 - 客户端代码需要知道服务器的URL以及要调用的服务接口。Hessian库会自动处理序列化和反序列化,使得客户端可以像调用本地方法一样调用...

    hessian-4.0.7jar文件与源代码.rar

    5. **安全特性**:尽管Hessian协议本身不包含安全机制,但它可以与HTTPS结合使用,确保数据传输的安全性。 学习和研究Hessian源代码,可以帮助我们理解以下内容: - 如何实现高效的二进制序列化和反序列化算法。 -...

    hessian使用小例子

    与传统的基于XML的Web服务相比,Hessian在处理复杂数据结构时更加高效。此外,Hessian还支持类型安全的远程方法调用,这意味着客户端和服务端可以自动匹配方法签名,减少编码错误。 现在,让我们通过一个简单的Java...

    hessian服务端 客户端 可运行

    Hessian是一种基于二进制协议的服务,它允许Java应用程序通过HTTP进行远程方法调用(RMI)。这个协议设计的目标是提供轻量级、高效的RPC(Remote Procedure Call)解决方案,尤其适用于内部网络或需要低延迟通信的...

Global site tag (gtag.js) - Google Analytics