手写基于 http 的RPC框架

时间:2022-10-24 21:56:44

一、模块划分

聚合工程

*模块:​new_rpc​

父模块:​httpclient_rpc​

子模块:​​httpclient_rpc_server、httpclient_rpc_client​、​httpclient_rpc_pojo​​

使用*模块是因为rpc协议自定义了两种方式,现在介绍的是​​httpclient_rpc​​这种方式

二、pom依赖

1.new_rpc

<groupId>com.zhkucst</groupId>
<artifactId>new_rpc</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>httpclient_rpc</module>
</modules>

2.httpclient_rpc

<parent>
<artifactId>new_rpc</artifactId>
<groupId>com.zhkucst</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>


<artifactId>httpclient_rpc</artifactId>
<packaging>pom</packaging>
<modules>
<module>httpclient_rpc_server</module>
<module>httpclient_rpc_client</module>
<module>httpclient_rpc_pojo</module>
</modules>

3.httpclient_rpc_pojo

<parent>
<artifactId>httpclient_rpc</artifactId>
<groupId>com.zhkucst</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>


<artifactId>httpclient_rpc_pojo</artifactId>

4.httpclient_rpc_server

<parent>
<artifactId>httpclient_rpc</artifactId>
<groupId>com.zhkucst</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>httpclient_rpc_server</artifactId>


<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.2.RELEASE</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>


<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.zhkucst</groupId>
<artifactId>httpclient_rpc_pojo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

5.httpclient_rpc_client

<parent>
<artifactId>httpclient_rpc</artifactId>
<groupId>com.zhkucst</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>


<artifactId>httpclient_rpc_client</artifactId>


<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.2.RELEASE</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>


<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--httpclient 依赖-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>com.zhkucst</groupId>
<artifactId>httpclient_rpc_pojo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.11.1</version>
</dependency>
</dependencies>

三、内容

1.httpclient_rpc_pojo

pojo类

package com.zhkucst.httpclient.pojo;


import sun.security.jca.GetInstance;


import java.io.Serializable;
import java.util.Date;


/**
* @version v1.0
* @ClassName: User
* @Description: TODO
* @Author: fyp
* @data: 2021年 11月 07日 14:55
*/
public class User implements Serializable {


private String name;
private String password;
private Date birth;


public int getAge(){
if(birth == null){
return -1;
}
int birthYear = birth.getYear();
int currentYear = new Date().getYear();
return currentYear - birthYear;
}


public Date getBirth() {
return birth;
}


public void setBirth(Date birth) {
this.birth = birth;
}


public String getName() {
return name;
}


public void setName(String name) {
this.name = name;
}


public String getPassword() {
return password;
}


public void setPassword(String password) {
this.password = password;
}


@Override
public String toString() {
return "{\"name\":\""+ name +"\",\"password\":\""+ password +"\"}";
}
}

2.httpclient_rpc_server

服务端控制器,即客户端发起请求的控制区

package com.zhkucst.httpclientserver.controller;


import com.zhkucst.httpclient.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;


import java.util.List;


/**
* @version v1.0
* @ClassName: TestController
* @Description: TODO
* @Author: fyp
* @data: 2021年 10月 18日 21:33
*/
@Controller
public class TestController {
/**
* CrossOrigin - 跨域请求注解。在响应头上增加跨域处理允许,可以让ajax跨越请求当前的服务方法
* @param users
* @return
*/
//使用请求体传递请求参数
@RequestMapping(value = "/bodyParams", produces = {"application/json;charset=UTF-8"})
@ResponseBody
@CrossOrigin
public String bodyParams(@RequestBody List<User> users){
System.out.println(users);
return users.toString();
}


@RequestMapping(value = "/params", produces = {"application/json;charset=UTF-8"})
@ResponseBody
public String params(String name, String password){
System.out.println("name - " + name + " ; password - " + password);
return "{\"msg\":\"登录成功\",\"user\":{\"name\":\""+name+"\",\"password\":\""+password+"\"}}";
}


@RequestMapping(value = "/test", produces = {"application/json;charset=UTF-8"})
@ResponseBody
public String test(){
return "{\"msg\":\"处理返回\"}";
}
}

3.httpclient_rpc_client

通过后端实现访问请求,后端以SpringBoot方式启动,前端直接java访问

package com.zhkucst.httpclient;


import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhkucst.httpclient.pojo.User;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;


import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;


/**
* @version v1.0
* @ClassName: TestHttpClient
* @Description: TODO
* @Author: fyp
* @data: 2021年 10月 18日 21:46
*/


public class TestHttpClient {
public static void main(String[] args) throws Exception{
//testGetNoParams();
//testGetParams();
testPost();
//testGetParams();
}


/**
* 有参Get请求
* @throws IOException
* @throws URISyntaxException
*/
public static void testGetParams() throws IOException, URISyntaxException {
//基于Builder构建请求地址
HttpClient client = HttpClients.createDefault();
URIBuilder builder = new URIBuilder("http://localhost/params");
//基于单参数传递,构建请求地址
//builder.addParameter("name", "bjsxt");
//builder.addParameter("password", "admin123");
//URI uri = builder.build();


//基于多参数传递,构建请求地址
List<NameValuePair> nvps = new ArrayList<>();
nvps.add(new BasicNameValuePair("name", "bjsxt"));
nvps.add(new BasicNameValuePair("password", "admin123"));
builder.addParameters(nvps);
URI uri = builder.build();


String result = EntityUtils.toString(client.execute(new HttpGet(uri)).getEntity());
System.out.println(result);


}


/**
* 无参数GET请求
* 使用浏览器,访问网站的过程是:
* 1、打开浏览器
* 2、输入网址
* 3、访问
* 4、看结果
* 使用HttpClient,访问WEB服务的过程:
* 1、创建客户端,相当于打开浏览器
* 2、创建请求地址,相当于输入地址
* 3、发起请求,相当于访问网站(回车键)
* 4、处理响应结果,相当于浏览器显示结果
*/
private static void testGetNoParams() throws IOException {
// 创建客户端对象
HttpClient client = HttpClients.createDefault();
// 创建请求地址
HttpGet get = new HttpGet("http://localhost:80/test");
// 发起请求,接收响应对象
HttpResponse response = client.execute(get);
// 获取响应体,响应数据是一个基于HTTP协议标准字符串封装的对象
HttpEntity entity = response.getEntity();
// 通过HTT实体工具类,转换响应体数据,使用的字符串是UTF-8
String responseString = EntityUtils.toString(entity, "UTF-8");


System.out.println("服务器响应的数据是 - [ " + responseString + "]");


client = null;
}


/**
* POST请求
*/
public static void testPost() throws Exception {
//无参数Post请求
HttpClient client = HttpClients.createDefault();
HttpPost post = new HttpPost("http://localhost/test");
HttpResponse response = client.execute(post);
System.out.println(EntityUtils.toString(response.getEntity(), "UTF-8"));


//有参数的Post请求
//请求头传递参数,和Get请求携带参数的方式一致
URIBuilder builder = new URIBuilder("http://localhost/params");
builder.addParameter("name", "post");
builder.addParameter("password", "postPassword");
URI uri = builder.build();
HttpResponse postResponse = client.execute(new HttpPost(uri));
System.out.println(EntityUtils.toString(postResponse.getEntity(), "UTF-8"));


//请求体传递参数
HttpPost bodyParamsPost = new HttpPost("http://localhost/bodyParams");
//定义请求体协议,设置请求参数。使用请求体传递参数的时候,需要定义请求体格式。默认是表单格式
//使用URLBuilder构建的URI对象,就是请求体传递参数的。
User u1 = new User();
u1.setName("zs");
u1.setPassword("123");
User u2 = new User();
u2.setName("ls");
u2.setPassword("456");


//手动json格式化
//String paramsString = "[" + u1.toString() + "," + u2.toString() + "]";


//使用jackson框架格式化
List<User> list = new ArrayList<>();
list.add(u1);
list.add(u2);
String paramsString;
ObjectMapper om = new ObjectMapper();
paramsString = om.writeValueAsString(list);


HttpEntity entity = new StringEntity(paramsString, "application/json", "UTF-8");
bodyParamsPost.setEntity(entity);
System.out.println(EntityUtils.toString(client.execute(bodyParamsPost).getEntity(), "UTF-8"));
String responseString = EntityUtils.toString(client.execute(bodyParamsPost).getEntity(), "UTF-8");
//将一个json格式的对象转换为java对象
String userString = responseString.substring(1, responseString.indexOf("},") + 1);
User responseUser = om.readValue(userString, User.class);
System.out.println(responseUser);
//将以json格式的对象数组转换为java对象数组
JavaType valueType = om.getTypeFactory().constructParametricType(List.class, User.class);
List<User> responseUserList = om.readValue(responseString, valueType);
System.out.println(responseUserList);


}

}

通过页面的​​ajax​​​请求访问,后端以​​springboot​​启动,前端以​web​​​启动

注意需要导入​​jquery.js​​包才可使用ajax请求,这里如果使用了跨域请求,也就是前后端分离请求,就需要在后端设置​​@CrossOrigin​​来允许跨域访问

手写基于 http 的RPC框架

手写基于 http 的RPC框架

后端没有设置​​@CrossOrigin​​会导致跨域请求而访问失败

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="jquery-3.5.1.min.js"></script>
<script type="text/javascript">
//javascript中默认的,ajax请求不能跨域
//跨域 - ajax所属的站点,和被请求的站点,不是同一个域
//域 - id,端口,域名,主机名,任何一个有变化,都是不同域
function sendBodyParams(){
$.ajax({
"url": "http://localhost:80/bodyParams",
"type": "post",
"data": "[{\"name\":\"abc\",\"password\":\"123\"},{\"name\":\"def\",\"password\":\"456\"}]",
"contentType": "application/json",//必须设定,代表请求体的格式。默认是text/plain,默认是参数名=参数值&参数名=参数值
"dataType": "json",
"success": function(data){
alert(data);
console.log(data);
}
});
}
</script>
</head>
<body style="text-align: center">
<button onclick="sendBodyParams()">测试ajax请求,请求体传递JSON数据</button>
</body>
</html>