前言:打完SCTF准备复现hellojava,发现之前学的那点链子根本不够用,在学一个新的链子之前,先把它本身了解一下比较好

什么是Hessian2

官方解答:

The Hessian binary web service protocol makes web services usable without requiring a large framework, and without learning yet another alphabet soup of protocols. Because it is a binary protocol, it is well-suited to sending binary data without any need to extend the protocol with attachments.

总结一下,Hessian是一种用于在网络上交换数据和调用远程方法的二进制协议,它可以在不同的语言和平台之间实现互操作性。它使用了一种简单而高效的二进制编码格式,可以支持复杂的对象和集合,而不需要额外的元数据或模式定义。Hessian可以作为一个轻量级的替代方案,来代替XML、SOAP或其他基于文本的协议。

根据“跨平台”,“二进制”等特性,我们可以看出,Hessian在序列化和远程调用方面大有可为,本篇文章就了解一下Hessian在和远程调用和序列化方面的功能

在JAVA中使用Hessian2

一.远程过程调用(RPC)

服务端Hessian_Service

在idea中创建一个maven的webapp项目作为RPC的服务端

创建2个包,一个Service服务接口,一个Impl用于实现接口

分别编写接口和实现方法

HelloService

1
2
3
4
5
6
7
package org.zhb.Service;

//服务接口
public interface HelloService {
public void Hello();
}

HelloServiceImpl

1
2
3
4
5
6
7
8
9
10
package org.zhb.Impl;
import org.zhb.Service.HelloService;
public class HelloServiceImpl implements HelloService {
public HelloServiceImpl() {
}
public void Hello() {
System.out.println("hello Lanb0");
}
}

第二步,编辑pom.xml,添加Hessian的第三方组件

1
2
3
4
5
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.66</version>
</dependency>

4.0以上的hessian默认使用Hessian2协议

第三步,找到webapp/WEB-INF/web.xml,在<web-app>标签里添加如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<servlet>
<servlet-name>HelloService</servlet-name>
<servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class>
<init-param>
<param-name>home-api</param-name>
<param-value>org.zhb.Service.HelloService</param-value>
</init-param>
<init-param>
<param-name>home-class</param-name>
<param-value>org.zhb.Impl.HelloServiceImpl</param-value>
</init-param>

</servlet>


<servlet-mapping>
<servlet-name>HelloService</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>

这里面具体是什么意思呢,简单说说

**<servlet>**标签表示定义一个servlet,你可以把他当做一个对象

**<servlet-name>**子标签相当于给这个servlet起一个名字(唯一性),

**<servlet-class>**子标签表示这个servlet的实现类是com.caucho.hessian.server.HessianServlet,这个类是Hessian组件提供的一个通用的servlet类,可以处理Hessian协议的请求和响应

<init-param>子标签用于指定这个servlet的初始化参数,也就是告诉HessianServlet,你用于提供远程调用的接口和实现类分别是什么,其中home-api指定接口,home-class指定对应的实现类

home-api和home-class是固定的命名,具体原因可以在com.caucho.hessian.server.HessianServlet的init方法中找到

**<servlet-mapping>**标签就是定义路由和servlet的映射关系,比较好理解

最后一步,配置tomcat,这里除了tomcat10之外都行,我用的是tomcat8,因为tomcat10把javax库改名为jakarta,而hessian用到了javax,踩坑踩了好久。。。

Tomcat需要注意的一点就是Deployment下的Application context相当于一个根路由,举个例子,如果我把Application context设置为method,那么我如果要访问原本的**/hello路由就要改为/method/hello**

客户端Hessian_Client

客户端就只需要远程调用一下hessian,所以写起来很简单

创建一个同样的HelloService接口

此处省略

借助Hessian进行RPC调用

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) throws MalformedURLException {

String url ="http://localhost:8080/method/hello";
//创建Hessian代理工厂类
HessianProxyFactory factory = new HessianProxyFactory();
//通过代理工厂去进行RPC调用
//这里需要向下转型因为create方法返回的是Object,需要显式转换
HelloService helloService =(HelloService) factory.create(HelloService.class,url);
//客户端从服务端接收执行结果
helloService.Hello();
}

不论是RMI还是RPC,方法的执行过程都是在服务端上运行的,只是最后服务端会把结果通过网络传输给客户端,所以这里的Hello方法打印的”hello lanb0”只会显示在服务端

当然,不排除像动态类加载这样的机制还是把二进制代码直接发给客户端的

二.Hessian2的序列化

直接上代码吧,没啥可赘述的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package org.example;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;

public class Main {
public static void main(String[] args) throws IOException {
Car car = new Car("BYD",99999);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2Output he=new Hessian2Output(byteArrayOutputStream);
//序列化
he.writeObject(car);
he.flush();

//将存储在输出流的序列化内容转换为字节数组
byte[] bytes= byteArrayOutputStream.toByteArray();
//然后赋值在输入流中
ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(bytes);
//反序列化
//Hessian2Input获取输入流的内容,然后将其反序列化
Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);
Car car2 =(Car) hessian2Input.readObject();
System.out.println(car2);
}
}
class Car implements Serializable {
public String name;
public Integer price;

@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}

public Car(String name, Integer price) {
this.name = name;
this.price = price;
}
}

flush用于将Hessian2Output对象的输出缓冲区中的数据写入到输出流中。这样可以确保数据被完整地发送到目的地,而不会被留在缓冲区中

如果不写这行,就会报错:Exception in thread “main” java.io.EOFException: readObject: unexpected end of file

这是因为你并没有把序列化后的数据写入到输出流

总结

Hessian2的初步了解就到这差不多了,后面就开始研究下反序列化的链子吧