SpringBoot 服务器实现接口代理转发,还可以对数据进行二次处理哦

今天要实现一个功能:

用 Java+SpringBoot 服务器实现接口代理转发,对数据进行二次处理,然后返回给客户端。

背景:Java 服务器作为客户端的上游服务器,需要负责返回所有请求数据,即使不是自己提供的功能,也要负责请求对应服务器,并将正确结果返回。

目的:这样做可以实现接口服务统一,也能解决前端跨域问题。

这里列举两个场景。

场景一:调用其他服务器,将结果直接返回给客户端

要实现的功能是

浏览器请求:https://10.28.10.11:8081/proxy/homeList

希望请求转发到 http://10.28.11.20:4000/homeList

源码:

package com.maomao.apm.controller;

import au.com.bytecode.opencsv.CSVReader;
import com.pinganfu.dochelper.annotation.PAFDoc;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

@RestController
public class ProxyController extends BaseController {

    private String targetAddr = "https://10.28.10.11:4000";

    /**
     * 代理所有请求
     *
     * @param request
     * @param response
     * @throws Exception
     */
    @RequestMapping(value = "/proxy/**", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public void proxy(HttpServletRequest request, HttpServletResponse response) throws IOException, URISyntaxException {
        // String url = URLDecoder.decode(request.getRequestURL().toString(), "UTF-8");
        URI uri = new URI(request.getRequestURI());
        String path = uri.getPath();
        String query = request.getQueryString();
        String target = targetAddr + path.replace("/proxy", "");
        if (query != null && !query.equals("") && !query.equals("null")) {
            target = target + "?" + query;
        }
        URI newUri = new URI(target);
        // 执行代理查询
        String methodName = request.getMethod();
        HttpMethod httpMethod = HttpMethod.resolve(methodName);
        if (httpMethod == null) {
            return;
        }
        ClientHttpRequest delegate = new SimpleClientHttpRequestFactory().createRequest(newUri, httpMethod);
        Enumeration<String> headerNames = request.getHeaderNames();
        // 设置请求头
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            Enumeration<String> v = request.getHeaders(headerName);
            List<String> arr = new ArrayList<>();
            while (v.hasMoreElements()) {
                arr.add(v.nextElement());
            }
            delegate.getHeaders().addAll(headerName, arr);
        }
        StreamUtils.copy(request.getInputStream(), delegate.getBody());
        // 执行远程调用
        ClientHttpResponse clientHttpResponse = delegate.execute();
        response.setStatus(clientHttpResponse.getStatusCode().value());
        // 设置响应头
        clientHttpResponse.getHeaders().forEach((key, value) -> value.forEach(it -> {
            response.setHeader(key, it);
        }));
        StreamUtils.copy(clientHttpResponse.getBody(), response.getOutputStream());
    }

}

原理是代理服务器(当前代码所在服务器)在接收到客户端的请求时,新建一个请求去请求真实服务器地址,并且把客户端请求地址里 “proxy” 关键字删除。等真实服务器返回数据后,把数据直接返回给客户端。

这个方法做到了统一,假设是 A 客户端请求 B 服务器实际获取 C 服务器的内容,这段代码可以做到 B 服务器请求 c 服务器的所有接口都能代理。只需要客户端请求时,在接口里加上 “proxy” 字样。

场景二:调用其他服务器,将结果进行处理后返回给客户端

场景一可以解决大部分代理问题。但是如果你需要对 C 服务器返回的数据进行二次加工,只需要做一点点改动。

这里举例一个 B 服务器获取 c 服务器的 CSV 文件,并将返回的 CSV 文件流转换成 Json 返回给客户端。

要实现的功能是

浏览器请求:https://10.28.10.11:8081/proxy/homedetail.csv

希望请求转发到 http://10.28.11.187:8088/homedetail.csv

最后解析 homedetail.csv 并返回 json 数组

源码:

package com.maomao.apm.controller;

import au.com.bytecode.opencsv.CSVReader;
import com.pinganfu.dochelper.annotation.PAFDoc;
import org.apache.logging.log4j.LogManager;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

@RestController
public class ProxyController extends BaseController {

    private String targetAddrUba = "http://10.28.11.187:8088";

    /**
     * 代理ubaReport请求所有请求
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @RequestMapping(value = "/ubaProxy/**")
    public List<String[]> proxyUba(HttpServletRequest request, HttpServletResponse response) throws IOException, URISyntaxException {
        // String url = URLDecoder.decode(request.getRequestURL().toString(), "UTF-8");
        URI uri = new URI(request.getRequestURI());
        String path = uri.getPath();
        String query = request.getQueryString();
        String target = targetAddrUba + path.replace("/ubaProxy", "");
        if (query != null && !query.equals("") && !query.equals("null")) {
            target = target + "?" + query;
        }
        URI newUri = new URI(target);
        // 执行代理查询
        String methodName = request.getMethod();
        HttpMethod httpMethod = HttpMethod.resolve(methodName);
        if (httpMethod == null) {
            return null;
        }
        ClientHttpRequest delegate = new SimpleClientHttpRequestFactory().createRequest(newUri, httpMethod);
        Enumeration<String> headerNames = request.getHeaderNames();
        // 设置请求头
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            Enumeration<String> v = request.getHeaders(headerName);
            List<String> arr = new ArrayList<>();
            while (v.hasMoreElements()) {
                arr.add(v.nextElement());
            }
            delegate.getHeaders().addAll(headerName, arr);
        }
        StreamUtils.copy(request.getInputStream(), delegate.getBody());
        // 执行远程调用
        ClientHttpResponse clientHttpResponse = delegate.execute();
//        response.setStatus(clientHttpResponse.getStatusCode().value());
        // 设置响应头
//        clientHttpResponse.getHeaders().forEach((key, value) -> value.forEach(it -> {
//            response.setHeader(key, it);
//        }));
        CSVReader csvReader = new CSVReader(new InputStreamReader(clientHttpResponse.getBody(), "utf-8"));
        List<String[]> stringsList = csvReader.readAll();
        return stringsList;
    }
}

pom.xml

<dependency>
            <groupId>net.sf.opencsv</groupId>
            <artifactId>opencsv</artifactId>
            <version>2.3</version>
        </dependency>

        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20090211</version>
        </dependency>

这里用了一个 “ubaProxy” 作为该请求功能的关键字。原理跟场景一相同。只是在返回数据之后对数据进行了CSV转Json的操作。具体代码:

CSVReader csvReader = new CSVReader(new InputStreamReader(clientHttpResponse.getBody(), "utf-8"));
        List<String[]> stringsList = csvReader.readAll();
        return stringsList;

注意,上面代码里将response.setHeader()相关代码注释掉了。原因是经过二次加工返回的数据,与加工前的数据长度是不一样的,如果复用加工前的 reponse header,会导致content-length数值不准确。如果加工后的数据比原始数据多,而content-length数值还是加工前的数值,会导致返回给客户端的数据不完整。如果不填,默认会返回所有数据。

参考文档:《Java+springboot 实现 nginx 反向代理功能》《JAVA:将 CSV 文件转换成 JSON》

path 不断重复打印,导致内存溢出

中间还遇到在打印代码中的path时,path的值一直在循环重复,控制台不停地打印,看起来像死循环了。

最后服务器撑不住,可能回报内存溢出的异常。

java.lang.OutOfMemoryError: Java heap space

java.lang.OutOfMemoryError: Java heap space         at java.util.Arrays.copyOfRange(Arrays.java:3664

解决办法

网上有很多答案是说把 JVM 的内存设置得更大一些:

手动设置Heap size

修改TOMCAT_HOME/bin/catalina.bat,在“echo "Using CATALINA_BASE: $CATALINA_BASE"”上面加入以下行:

Java代码

set JAVA_OPTS=%JAVA_OPTS% -server -Xms800m -Xmx800m -XX:MaxNewSize=256m

或将application.properties里面的大小值设置得更大一些。

//src/main/resources/application.properties

#将文件写入磁盘的阈值。值可以使用后缀“MB”或“KB”分别表示兆字节或千字节。
spring.servlet.multipart.file-size-threshold=2KB

#设置单个文件的大小,
spring.servlet.multipart.max-file-size=100MB

#最大请求大小。值可以使用后缀“MB”或“KB”分别表示兆字节或千字节。
spring.servlet.multipart.max-request-size=100MB

但是我这里不是因为内存设置不够引起的。

出问题的原因是类名上面使用了@Controller注解,把它改成@RestController就好了。

原理是@Controller注解会认为请求的是一个页面,而不是数据,如果没找到页面,会继续转发往下找,如果一直找不到,就死循环了。而@RestController注解只关心请求的内容,请求到内容就直接返回,请求不到内容也返回空数据,不会进行转发。


作者:"毛毛"

来源链接:

https://maomao.ink/index.php/IT/1644.html

4996f6ef60e251357eab626012eedca2.png

  • 11
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
Spring Boot中可以通过组合使用代理服务器HTTP请求转发实现代理转发。具体步骤如下: 1. 配置代理服务器 在application.properties文件中配置代理服务器的地址和端口号: ``` # 配置代理服务器地址和端口号 spring.proxies.host=proxy.example.com spring.proxies.port=8080 ``` 2. 创建RestTemplate对象 在创建RestTemplate对象时,需要设置代理服务器的地址和端口号: ```java @Configuration public class RestTemplateConfiguration { @Value("${spring.proxies.host}") private String proxyHost; @Value("${spring.proxies.port}") private int proxyPort; @Bean public RestTemplate restTemplate() { SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); requestFactory.setProxy(proxy); return new RestTemplate(requestFactory); } } ``` 3. 创建转发服务 创建一个转发服务,用于将HTTP请求转发到目标服务: ```java @RestController public class ForwardController { private final RestTemplate restTemplate; public ForwardController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @GetMapping("/forward") public String forward(HttpServletRequest request) { // 获取目标URL String url = request.getParameter("url"); // 发送HTTP请求获取响应 ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class); // 返回响应 return responseEntity.getBody(); } } ``` 4. 创建代理服务 创建一个代理服务,用于接收HTTP请求并将请求转发转发服务: ```java @RestController public class ProxyController { private final RestTemplate restTemplate; public ProxyController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @GetMapping("/proxy") public String proxy(HttpServletRequest request) { // 获取转发URL String url = request.getParameter("url"); // 构造转发URL String forwardUrl = "http://localhost:8080/forward?url=" + url; // 发送HTTP请求获取响应 ResponseEntity<String> responseEntity = restTemplate.getForEntity(forwardUrl, String.class); // 返回响应 return responseEntity.getBody(); } } ``` 5. 配置路由规则 在application.properties文件中配置路由规则: ``` # 配置路由规则 spring.cloud.gateway.routes[0].id=proxy spring.cloud.gateway.routes[0].uri=http://localhost:8081 spring.cloud.gateway.routes[0].predicates[0]=Path=/proxy/** ``` 6. 启动服务 启动代理服务和转发服务,访问代理服务的URL即可实现代理转发。 以上就是使用Spring Boot实现代理转发的步骤。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值