Java+SpringBoot实现接口代理转发
RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。RestTemplate 继承InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现。
RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端库。RestTemplate的请求方式包括了以下几种:
- getForEntity
- getForObject
- postForEntity
- put
- delete
场景需求
本次的一个需求场景是:
用Java+Springboot服务器实现接口代理的二次转发,即接口接受到请求后,需要将请求转发到另一处服务器上,并将请求返回的结果返回给客户端
背景:Java 服务器作为客户端的上游服务器,需要负责返回所有请求数据,即使不是自己提供的功能,也要负责请求对应服务器,并将正确结果返回。
目的:这样做可以实现接口服务统一,也能解决前端跨域问题。
在调研了Java发送Http请求的多种方法后,选择了采用上述RestTemplate工具来实现。
为了更好的给出模拟场景,给出返回结果,因此本文首先设计一个简单的服务器来返回结果,而不是转发到目前互联网上的某一地址,比如百度、微博等(不易观察返回结果)
源码
首先给出一个简易服务器的实现源码。基于Socket来实现,等待客户端连接,并在有客户端连接后,返回特定的数据结构。
Server
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Server {
public static void main(String[] args)
{
ServerSocket ss = null;
Socket s = null;
List<Socket> listSockets = new ArrayList<Socket>();
// 1. 创建ServerSocket类型的对象并提供端口号
try {
ss = new ServerSocket(9999);
// 2. 等待客户端的连接请求,调用accept方法
while(true) {
System.out.println("等待客户端的连接请求....");
s = ss.accept();
// 阻塞,直到有客户端连接
InetAddress inetAddress = s.getInetAddress();
System.out.println("客户端" + inetAddress + "已连接");
// 测试样例 返回一个Map类型对象给客户端
Map<String, Object> map = new HashMap<String, Object>();
map.put("code", 200);
map.put("message", "测试样例message");
map.put("success", true);
String str = JSONObject.toJSONString(map);
OutputStream outputStream = s.getOutputStream();
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
BufferedWriter bufferedWriter =new BufferedWriter(outputStreamWriter);
// 由于需要响应http请求,所以需要加上HTTP标识
bufferedWriter.write("HTTP/1.1 200 OK\n");
bufferedWriter.write("Content-Type:text/html;charset:utf-8\n");
bufferedWriter.write("\n");
bufferedWriter.write(str);
bufferedWriter.flush();
s.close();
ss.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
有了服务器后,我们就可以测试我们的接口代理二次转发功能了。想法就是接口在收到请求后,根据请求的url,对其中的某些部分进行替换(因为转发的url跟请求的url是有关系的),然后再将新的url发送Http请求到新的服务器上获取返回结果。
接口代理二次转发源码:
@RestController
@RequestMapping(value = "/proxy")
public class ProxyCheck{
@RequestMapping(value="/restTemplate", method = RequestMethod.GET)
public Map<String, Object> apiProxyGet (HttpServletRequest request, HttpServletResponse res) throws UnsupportedEncodingException {
// 获取请求url
String url = request.getRequestURL().toString();
// 获取url中携带的参数,即/resTemplate?后面的内容
String query = request.getQueryString();
String target = "http://127.0.0.1:9999";
// 新的url拼接上旧url后的请求参数
if (query != null && !query.equals("") && !query.equals("null")) {
target = target + "?" + query;
}
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
// 获取到请求头
Enumeration<String> headerNames = request.getHeaderNames();
HttpHeaders headers = new HttpHeaders();
Map<String, String> headerMap = new HashMap<>();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String header = request.getHeader(headerName);
headerMap.put(headerName, header);
headers.add(headerName, header);
}
// 构造HttpEntity,新请求会携带本次请求的请求头
HttpEntity entity = new HttpEntity<String>(headers);
ResponseEntity<String> response = restTemplate.getForEntity(target, String.class, entity);
Map<String, Object> list = JSONArray.parseObject(response.getBody(), Map.class);
// 当需要分页时,返回分页信息
res.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
res.setHeader("Content-Range", String.valueOf(list.size()));
res.setHeader("Access-Control-Expose-Headers", "Content-Range");
return list;
}
有了上述代码后,我们便可以进行测试。测试利用postman来发送请求。
首先启动Server,然后根据Controller定义的请求路径,在postman中发送请求,可以看到服务端和postman的返回结果为:
Server:
Postman返回结果:
可以看到我们在服务端定义的测试样例数据,经过接口的二次转发,成功进行了返回,实现了我们最开始的需求场景。本次的测试样例虽然简单,但是背景是在企业开发需求中真实遇到的,对场景进行了简化,但关键技术仍然进行了展示。最后代码中给出了携带请求头和需要分页时的解决方法,在某些场景下仍然受用。