Hessian客户端向服务端发送请求头

Hessian客户端向服务端发送数据
场景:项目日志Token处理,即用户发送一个请求时生成一个日志Token,该Token从各个服务之间传递,并使用该Token记录日志,直至请求结束。可以根据该Token定位所有日志。
问题:由于目前项目使用Hessian协议,所有Token必须使用Hessian传递。查阅相关资料,发现可以请求头传递数据。
解决方法:定义与线程相关的请求头上下文,在客户端发送请求之前,增加请求头。服务端获取请求时,从请求中解决请求头,并放入请求头上下文中,供服务端使用。
实现:
1.定义请求头上下文HessianHeaderContext,代码如下:

package org.enyes.hessian

/**
 * Hessian协议请求头上下文。
 * <pre>
 *  1.该类使用ThreadLocal将客户端请求头信息传递给服务端。
 *  2.请求头在HessianProxy发送请求之前,将该类中得请求头附加到请求中。
 *  3.服务端使用HessianServiceExporter中获取请求头,并放入HessianHeaderContext中,提供服务端使用。
 *  4.使用完记得调用#close方法,防止ThreadLocal内存泄露。
 * </pre>
 * Created by enyes on 15/8/30.
 * @see HessianProxy
 * @see HessianProxyFactory
 * @see HessianServiceExporter
 */
public class HessianHeaderContext {

    private static final ThreadLocal<HessianHeaderContext> THREAD_LOCAL = new ThreadLocal<>();

    private Map<String, String> headers = new HashMap<>();


    private HessianHeaderContext() {
    }

    public static HessianHeaderContext getContext() {
        HessianHeaderContext context = THREAD_LOCAL.get();
        if (context == null) {
            context = new HessianHeaderContext();
            THREAD_LOCAL.set(context);
        }
        return context;
    }

    public void addHeader(String name, String value) {
        headers.put(name, value);
    }

    public String getHeader(String name) {
        return headers.get(name);
    }

    public Map<String, String> getHeaders() {
        return headers;
    }

    public static void close() {
        HessianHeaderContext context = THREAD_LOCAL.get();
        if (context != null) {
            context.headers.clear();
            THREAD_LOCAL.set(null);
        }
    }
}

2.拓展客户端HessianProxy,在发送请求时添加上下文请求头,如下:

/**
 * 拓展HessianProxy,在客户端发送请求之前,将HessianHeaderContext中的请求头添加到请求中。
 * Created by enyes on 15/8/30.
 * @see org.enyes.hessian.HessianProxyFactory
 */
public class HessianProxy extends com.caucho.hessian.client.HessianProxy {

    protected HessianProxy(URL url, HessianProxyFactory factory) {
        super(url, factory);
    }

    protected HessianProxy(URL url, HessianProxyFactory factory, Class<?> type) {
        super(url, factory, type);
    }

    @Override
    protected void addRequestHeaders(HessianConnection conn)
    {
        super.addRequestHeaders(conn);

        // add Hessian Header
        Map<String, String> headerMap = HessianHeaderContext.getContext().getHeaders();
        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            conn.addHeader(entry.getKey(), entry.getValue());
        }
    }
}

3.重写HessianProxyFactory,集成新拓展的HessianProxy,如下:

/**
 * 拓展HessianProxyFactory,使用新拓展的HessianProxy。
 * <pre>
 *  注:如果使用Spring集成,HessianProxyFactoryBean需要设置proxyFactory为该类对象。
 * </pre>
 * Created by enyes on 15/8/30.
 * @see HessianProxy
 */
public class HessianProxyFactory extends com.caucho.hessian.client.HessianProxyFactory {

    @Override
    public Object create(Class<?> api, URL url, ClassLoader loader)
    {
        if (api == null) {
            throw new NullPointerException("api must not be null for HessianProxyFactory.create()");
        }

        InvocationHandler handler = new HessianProxy(url, this, api);

        return Proxy.newProxyInstance(loader,
                new Class[]{api, HessianRemoteObject.class},
                handler);
    }

}

注意:HessianProxy与HessianProxyFactory命名仍与hessian.jar中一致,一时没有想到合适的名称。
4.由于项目与Spring集成,所以拓展Spring HessianServiceExporter,解析请求头。

/**
 * 拓展Spring HessianServiceExporter类,在服务端接受请求时,
 * 接收客户端请求头,并保存到HessianHeaderContext中,供服务端使用。
 * spring 服务端xml文件配置该类。
 * Created by enyes on 15/8/30.
 */
public class HessianServiceExporter extends org.springframework.remoting.caucho.HessianServiceExporter {


    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        if (!"POST".equals(request.getMethod())) {
            throw new HttpRequestMethodNotSupportedException(request.getMethod(),
                    new String[] {"POST"}, "HessianServiceExporter only supports POST requests");
        }

        handleHessianHeader(request);

        response.setContentType(CONTENT_TYPE_HESSIAN);
        try {
            invoke(request.getInputStream(), response.getOutputStream());
        } catch (Throwable ex) {
            throw new NestedServletException("Hessian skeleton invocation failed", ex);
        } finally {
            HessianHeaderContext.close();
        }
    }

    protected void handleHessianHeader(HttpServletRequest request) {
        HessianHeaderContext context = HessianHeaderContext.getContext();
        Enumeration enumeration = request.getHeaderNames();
        while (enumeration.hasMoreElements()) {
            String name = enumeration.nextElement().toString();
            String value = request.getHeader(name);
            context.addHeader(name, value);
        }
    }

}

5.客户端Spring配置文件修改:

<bean id="hessianProxyFactory" class="org.enyes.hessian.HessianProxyFactory" />

    <bean id="hessianServer" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
        <property name="proxyFactory" ref="hessianProxyFactory" />
        <property name="serviceUrl" value="${hessian.remote.provider_url}/hessianServer"/>
        <property name="serviceInterface" value="org.enyes.hessian.service.HessianServer"/>
        <property name="overloadEnabled" value="true" />
    </bean>

6.服务端Spring配置文件,如下:

<bean name="/hessianServer" class="org.enyes.hessian.HessianServiceExporter">
        <property name="service" ref="hessianServerImpl"/>
        <property name="serviceInterface" value="org.enyes.hessian.service.HessianServer"/>
    </bean>

7.至次,客户端向HessianHeaderContext添加请求头后,服务端的HessianHeaderContext都能获取到。

Controller测试

@Controller()
@RequestMapping("/hessian")
public class HessianController {

    @Autowired
    private HessianServer hessianServer;

    @RequestMapping("passHeader")
    @ResponseBody
    public String passHeader() {
        HessianHeaderContext context = HessianHeaderContext.getContext();
        context.addHeader("log.token", "logToken_111111");
        hessianServer.passHeader();
        HessianHeaderContext.close();
        return "success";
    }


}

Junit测试

@Test
public void testPassHeader() throws MalformedURLException {
        //Spring Hessian代理Servlet
        String url = "http://localhost:8081/demoProvider/hessianServer";
        HessianProxyFactory factory = new HessianProxyFactory();
        HessianHeaderContext context = HessianHeaderContext.getContext();
        context.addHeader("log.token", UUID.randomUUID().toString());

        HessianServer api = (HessianServer) factory.create(HessianServer.class, url);
        api.passHeader();
    }

服务端Service:

@Service
public class HessianServerImpl implements HessianServer {

    private static final Logger LOG = LoggerFactory.getLogger(HessianServerImpl.class);

    @Override
    public String domainChange(User user) throws BusinessException {
        throw new IllegalArgumentException( "domainChange. user:" + user);
    }

    @Override
    public void passHeader() {
        HessianHeaderContext context = HessianHeaderContext.getContext();
        String logToken = context.getHeader("log.token");
        LOG.warn("passHeader. Header[log.token]={}", logToken);
    }
}

结束语:
在部署生产环境时,出现了logToken请求头丢失。原因是生成环境使用nginx做负载均衡,而nginx配置默认不转发带下划线的自定义参数,参加:http://m.oschina.net/blog/83945
本文提供一种思路,读者可以查看相关源码或者他人博客。

©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值