利用spring写一个反向代理

让spring服务器做为一个反向代理,将一些请求转发给其他的服务来完成响应。实现类似于nginx的功能。

思路:

1、写一个Filter来判断路径来转发符合规则的请求(只转发后端请求且符合特定规则的请求)

2、需要判断后端的服务是否存活

3、转发需要将表单之间的&转为最初的&

HTML中的&用& 来表示,转发过程中需要用
StringEscapeUtils.unescapeHtml3(queryString)来反解,将& 还原为&

代码:

java 复制代码
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;

import org.apache.commons.text.StringEscapeUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestTemplate;

import jakarta.annotation.Resource;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;

@Component
@Order(2)
@Slf4j
public class ForwardFilter implements Filter {

    @Value("${ha.service.host:}")
    private String masterServiceHost;

    @Value("${server.servlet.context-path}")
    private String apiBasePath;

    @Resource
    private HighAvailableService highAvailableService;

    @Override
    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse,
                         FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        if (request.getRequestURI().startsWith(apiBasePath) &&
            highAvailableService.isMasterAlive()) {
            // 转发请求
            try {
                final ResponseEntity<byte[]> responseEntity =
                        forward(request, response, masterServiceHost);
                HttpStatusCode statusCode = responseEntity.getStatusCode();
                if (statusCode.isError()) {
                    // get from self
                    filterChain.doFilter(servletRequest, servletResponse);
                } else {
                    // transfer response information
                    response.setContentType(
                            responseEntity.getHeaders().getContentType()
                                          .toString());
                    response.setContentLengthLong(
                            responseEntity.getHeaders().getContentLength());
                    response.setCharacterEncoding(
                            StandardCharsets.UTF_8.name());
                    response.setStatus(responseEntity.getStatusCodeValue());

                    final ContentDisposition contentDisposition =
                            responseEntity.getHeaders().getContentDisposition();
                    response.setHeader("Content-Disposition",
                                       contentDisposition.toString());

                    ServletOutputStream outputStream = response.getOutputStream();
                    outputStream.write(responseEntity.getBody());
                    outputStream.flush();
                    outputStream.close();
                }
            } catch (Exception e) {
                log.error("{}", e);
                response.setStatus(400);
                PrintWriter writer = response.getWriter();
                writer.write(e.getClass().getName());
                writer.flush();
            }
        } else {
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }

    public ResponseEntity<byte[]> forward(HttpServletRequest request,
                                          HttpServletResponse response,
                                          String routeUrl) {
        try {
            // build up the forward URL
            String forwardUrl = createForwardUrl(request, routeUrl);
            RequestEntity requestEntity =
                    createRequestEntity(request, forwardUrl);
            return route(requestEntity);
        } catch (Exception e) {
            return new ResponseEntity("FORWARD ERROR",
                                      HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    private String createForwardUrl(HttpServletRequest request,
                                    String routeUrl) {
        String queryString = request.getQueryString();
        final String decode = StringEscapeUtils.unescapeHtml3(queryString);
        String requestURI = request.getRequestURI();
        return routeUrl + requestURI + (decode != null ?
                                                "?" + decode : "");
    }

    private RequestEntity createRequestEntity(HttpServletRequest request,
                                              String url)
            throws URISyntaxException, IOException {
        String method = request.getMethod();
        HttpMethod httpMethod = HttpMethod.valueOf(method);
        MultiValueMap<String, String> headers = parseRequestHeader(request);
        byte[] body = parseRequestBody(request);
        return new RequestEntity<>(body, headers, httpMethod, new URI(url));
    }

    private ResponseEntity<byte[]> route(RequestEntity requestEntity) {
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.exchange(requestEntity, byte[].class);
    }

    private byte[] parseRequestBody(HttpServletRequest request)
            throws IOException {
        InputStream inputStream = request.getInputStream();
        return StreamUtils.copyToByteArray(inputStream);
    }

    private MultiValueMap<String, String> parseRequestHeader(
            HttpServletRequest request) {
        HttpHeaders headers = new HttpHeaders();
        List<String> headerNames = Collections.list(request.getHeaderNames());
        for (String headerName : headerNames) {
            List<String> headerValues =
                    Collections.list(request.getHeaders(headerName));
            for (String headerValue : headerValues) {
                headers.add(headerName, headerValue);
            }
        }
        return headers;
    }

}
相关推荐
严谨的麻辣烫2 小时前
静态 ISP 代理请求超时怎么排查?Python 与 curl 实战
python·http·requests·1024程序员节·网络排障
奇牙coding16 小时前
doubao-seed-2.1-pro 报 HTTP 401 怎么办?Key 正确也要检查请求头和端点
网络·数据库·http·ai
Lsetea1 天前
curl访问HTTPS报47 Too many redirects:重定向循环与max-redirs排查
linux·http·https·ssl证书·curl
在世修行1 天前
智学文枢:AI 客户端封装
python·http·ai·1024程序员节·pyside
紧跟先前的步伐2 天前
Go AI Agent 实战 12|HTTP 状态码、API 错误处理与日志
开发语言·人工智能·http·ai·golang
Lsetea3 天前
curl下载HTTPS正文是乱码:--compressed与Content-Encoding分层排查
linux·http·https·ssl证书·curl
刘胡子大叔3 天前
curl resolve 和 Host 头的两种免 DNS 验证 HTTPS
网络协议·http·https
j7~3 天前
【Linux网络加餐】《HTTP 会话保持:Cookie 与 Session 原理及实验验证》---详解
linux·网络·网络协议·http·cookie·session
mister_guo3 天前
HTTP 和 RPC:从一次干饭说清它们的区别
网络协议·http·rpc
严谨的麻辣烫3 天前
Python HTTP 连接池怎么用?从连接复用到请求异常排查
开发语言·python·http·requests·网络排查