SpringBoot实现轻量级接口反向代理、转发

前言:想实现一个轻量级的接口反向代理和转发的一个接口服务,可以通过这个服务做一些需要认证才能访问的接口给到前端使用,这样就实现了一种认证可以调用多种第三方系统的服务。

基本逻辑就是将请求的请求方式、请求头、请求体提取出来,将这些信息转发到另外一个接口

假设当前接口在 bizbook-api 这个服务上可以实现 /bizbook-api/common/** 的接口转发到 http://192.168.50.43:7612/**

列子:访问:/bizbook-api/common/bizweb-api/sj/list 就相当于访问 /bizweb-api/sj/list

1、基本的对象

1.1 配置类
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 路由代理配置
 */
@Configuration
@ConfigurationProperties(prefix = "delegate.config.api", ignoreUnknownFields = false)
public class RouterDelegateProperties {

    /**
     * 网关地址
     */
    String rootPath;

    /**
     * 服务名称, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
     */
    List<String> serviceName;

    /**
     * 服务网关, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
     */
    List<String> serviceRoot;

    /**
     * 服务拓展器, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
     */
    List<String> serviceExtractor;


    public String getRootPath() {
        return rootPath;
    }

    public void setRootPath(String rootPath) {
        this.rootPath = rootPath;
    }

    public List<String> getServiceName() {
        return serviceName;
    }

    public void setServiceName(List<String> serviceName) {
        this.serviceName = serviceName;
    }

    public List<String> getServiceRoot() {
        return serviceRoot;
    }

    public void setServiceRoot(List<String> serviceRoot) {
        this.serviceRoot = serviceRoot;
    }

    public List<String> getServiceExtractor() {
        return serviceExtractor;
    }

    public void setServiceExtractor(List<String> serviceExtractor) {
        this.serviceExtractor = serviceExtractor;
    }
}
1.2 实体DTO
import java.io.Serializable;

/**
 * 代理路由配置 DTO
 */
public class RouterDelegateConfigDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 网关地址
     */
    private String rootPath;

    /**
     * 服务名称
     */
    private String serviceName;

    /**
     * 服务名称地址
     */
    private String serviceRoot;

    /**
     * 服务名称处理器
     */
    private String serviceExtractor;


    public String getRootPath() {
        return rootPath;
    }

    public void setRootPath(String rootPath) {
        this.rootPath = rootPath;
    }

    public String getServiceName() {
        return serviceName;
    }

    public void setServiceName(String serviceName) {
        this.serviceName = serviceName;
    }

    public String getServiceRoot() {
        return serviceRoot;
    }

    public void setServiceRoot(String serviceRoot) {
        this.serviceRoot = serviceRoot;
    }

    public String getServiceExtractor() {
        return serviceExtractor;
    }

    public void setServiceExtractor(String serviceExtractor) {
        this.serviceExtractor = serviceExtractor;
    }
}
1.3 路由代理拓展器
import org.springframework.http.HttpHeaders;
import zsoft.gov.datacenter.biztable.common.dto.router.RouterDelegateConfigDTO;

import javax.servlet.http.HttpServletRequest;

/**
 * 路由代理拓展器
 */
public interface RouterDelegateExtractor {

    /**
     * 处理请求url, 返回null则使用通用处理逻辑
     *
     * @param request   请求体对象
     * @param configDTO 服务配置对象
     * @param prefix    代理前缀
     * @return
     */
    String getRequestRootUrl(HttpServletRequest request, RouterDelegateConfigDTO configDTO, String prefix);

    /**
     * 处理请求头
     *
     * @param request 请求体对象
     * @param headers 请求头
     */
    void parseRequestHeader(HttpServletRequest request, HttpHeaders headers);

    /**
     * 处理请求体, 返回null则使用通用处理逻辑
     *
     * @param request 请求体对象
     * @return
     */
    byte[] parseRequestBody(HttpServletRequest request);

}
1.4 请求对象 RestTemplate
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;

@Configuration
public class FetchApiRestTemplateConfig {

    @Bean({"fetchApiRestTemplate"})
    @Autowired
    public RestTemplate restTemplate(@Qualifier("fetchApiClientHttpRequestFactory") ClientHttpRequestFactory factory) {
        RestTemplate restTemplate = new RestTemplate(factory);
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
//                if (response.getRawStatusCode() != 401 && response.getRawStatusCode() != 404) {
//                    super.handleError(response);
//                }
                // 处理返回 4xx 的状态码时不抛出异常
                if (!response.getStatusCode().is4xxClientError()) {
                    super.handleError(response);
                }
            }
        });
        // 中文乱码问题
        List<HttpMessageConverter<?>> httpMessageConverters = restTemplate.getMessageConverters();
        httpMessageConverters.stream().forEach(httpMessageConverter -> {
            if (httpMessageConverter instanceof StringHttpMessageConverter) {
                StringHttpMessageConverter messageConverter = (StringHttpMessageConverter) httpMessageConverter;
                messageConverter.setDefaultCharset(Charset.forName("UTF-8"));
            }
        });
        return restTemplate;
    }

    @Bean({"fetchApiClientHttpRequestFactory"})
    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(1000 * 50); // 读取超时(毫秒)
        factory.setConnectTimeout(1000 * 10); // 连接超时(毫秒)
        return factory;
    }

}

2、核心转发代码

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestTemplate;
import zsoft.gov.datacenter.biztable.common.config.RouterDelegateProperties;
import zsoft.gov.datacenter.biztable.common.dto.router.RouterDelegateConfigDTO;
import zsoft.gov.datacenter.biztable.common.response.Result;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 路由代理
 */
@Service
public class RouterDelegate implements ApplicationRunner {

    protected Logger logger = LoggerFactory.getLogger(getClass());

    private static Map<String, RouterDelegateConfigDTO> configMap;

    @Resource
    @Qualifier("fetchApiRestTemplate")
    private RestTemplate restTemplate;

    @Resource
    private RouterDelegateProperties properties;

    @Resource
    private Map<String, RouterDelegateExtractor> stringRouterDelegateExtractorMap;


    /**
     * 初始化配置类
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        boolean intiFlag = false;
        logger.info(">>> -----开始初始化路由代理配置类!");

        /**
         * 最终configMap效果
         * {
         *     服务名称: {
         *         rootPath: "系统网关地址",
         *         serviceName: "服务名称",
         *         serviceRoot: "服务网关",
         *         serviceExtractor: "服务拓展器",
         *     }
         * }
         */
        String rootPath = properties.getRootPath();
        List<String> serviceName = properties.getServiceName();
        List<String> serviceRoot = properties.getServiceRoot();
        List<String> serviceExtractor = properties.getServiceExtractor();
        // 服务名称, 服务名称和服务网关和服务处理器一一对应, 如果没有对应的服务网关和服务处理器, 则用英文逗号隔开
        if (StringUtils.isNotBlank(rootPath)
                && CollectionUtils.isNotEmpty(serviceName)
                && CollectionUtils.isNotEmpty(serviceExtractor)
                && CollectionUtils.isNotEmpty(serviceRoot)
                && serviceName.size() == serviceRoot.size()
                && serviceName.size() == serviceExtractor.size()) {

            intiFlag = true;
            // 初始化大小避免扩容
            int initialCapacity = (int) (serviceName.size() / 0.75) + 1;
            configMap = new ConcurrentHashMap<>(initialCapacity);
            for (int i = 0; i < serviceName.size(); i++) {
                RouterDelegateConfigDTO dto = new RouterDelegateConfigDTO();
                String serName = serviceName.get(i);
                dto.setRootPath(rootPath);
                dto.setServiceName(serName);
                // default 是占位符, 配置成default相当于没有配置
                dto.setServiceRoot("default".equals(serviceRoot.get(i)) ? null : serviceRoot.get(i));
                dto.setServiceExtractor("default".equals(serviceExtractor.get(i)) ? null : serviceExtractor.get(i));
                configMap.put(serName, dto);
            }
        }

        if (intiFlag) logger.info(">>> 初始化路由代理配置类成功!");
        else logger.error(">>> 初始化路由代理配置类失败!");
    }

    public ResponseEntity<byte[]> redirect(HttpServletRequest request, HttpServletResponse response, String prefix, String serviceName) {
        String requestURI = request.getRequestURI();

        RouterDelegateConfigDTO currentConfig = getCurrentServiceConfig(serviceName);
        if (currentConfig == null) {
            return buildErrorResponseEntity("SERVICE ERROR! 服务不存在!", HttpStatus.NOT_FOUND);
        }
        RouterDelegateExtractor extractorCallBack = getRouterDelegateExtractor(serviceName);

        try {
            // 创建url
            String redirectUrl = createRequestUrl(request, currentConfig, prefix, extractorCallBack);
            logger.info(">>> redirectUrl代理后的完整地址: [{}]", redirectUrl);
            RequestEntity requestEntity = createRequestEntity(request, redirectUrl, extractorCallBack);
            // return route(request, redirectUrl, extractorCallBack);
            ResponseEntity<byte[]> result = route(requestEntity);
            if (result.getHeaders() != null && result.getHeaders().containsKey(HttpHeaders.TRANSFER_ENCODING)) {
                // 移除响应头 Transfer-Encoding, 因为高版本的nginx会自动添加该响应头, 多个响应值nginx会报错
                // 多个响应值nginx报错: *6889957 upstream sent duplicate header line: "Transfer-Encoding: chunked", previous value: "Transfer-Encoding: chunked" while reading response header from upstream
                HttpHeaders headers = HttpHeaders.writableHttpHeaders(result.getHeaders());
                headers.remove(HttpHeaders.TRANSFER_ENCODING);
            }
            //logger.info(">>> [{}] 代理成功, 请求耗时: [{}]", requestURI, System.currentTimeMillis() - l1);
            return result;
        } catch (Exception e) {
            logger.error("REDIRECT ERROR", e);
            //logger.error(">>> [{}] 代理失败, 请求耗时: [{}]", requestURI, System.currentTimeMillis() - l1);
            return buildErrorResponseEntity("REDIRECT ERROR! " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    private ResponseEntity buildErrorResponseEntity(String msg, HttpStatus httpStatus) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        Result body = Result.build(httpStatus.value(), msg);
        return new ResponseEntity(body, headers, httpStatus);
    }

    /**
     * 获取当前服务配置
     *
     * @param serviceName
     * @return
     */
    public RouterDelegateConfigDTO getCurrentServiceConfig(String serviceName) {
        if (configMap == null || !configMap.containsKey(serviceName)) {
            return null;
        }

        return configMap.get(serviceName);
    }

    /**
     * 获取当前路由服务拓展器
     *
     * @param serviceName
     * @return
     */
    private RouterDelegateExtractor getRouterDelegateExtractor(String serviceName) {
        RouterDelegateConfigDTO currentConfig = getCurrentServiceConfig(serviceName);
        if (currentConfig == null) {
            return null;
        }

        String serviceExtractor = currentConfig.getServiceExtractor();
        if (StringUtils.isBlank(serviceExtractor)) {
            return null;
        }

        RouterDelegateExtractor extractor = stringRouterDelegateExtractorMap.get(serviceExtractor + "RouterDelegateExtractor");
        return extractor;
    }

    /**
     * 创建请求地址
     *
     * @param request
     * @param configDTO
     * @param prefix
     * @param extractorCallback
     * @return
     */
    private String createRequestUrl(HttpServletRequest request, RouterDelegateConfigDTO configDTO, String prefix, RouterDelegateExtractor extractorCallback) {
        String routeUrl = configDTO.getRootPath();
        // 拓展器不为null, 并且有返回结果才使用
        if (extractorCallback != null) {
            String hostUrl = extractorCallback.getRequestRootUrl(request, configDTO, prefix);
            if (hostUrl != null) routeUrl = hostUrl;
        }

        String queryString = request.getQueryString();
//        return routeUrl + request.getRequestURI().replace(prefix, "") +
//                (queryString != null ? "?" + queryString : "");

        // request.getRequestURI() 包括 server.servlet.context-path
        // request.getServletPath() 不包括 server.servlet.context-path
        // http://127.0.0.1/databook-api/graphdb/sj/tianda/openapi/v1/applets?name=ts
        // request.getRequestURI() = /databook-api/graphdb/sj/tianda/openapi/v1/applets
        // request.getServletPath() = /graphdb/sj/tianda/openapi/v1/applets
        String serviceName = configDTO.getServiceName();
        return routeUrl + request.getServletPath().replaceFirst(prefix + "/" + serviceName, "") +
                (queryString != null ? "?" + queryString : "");
    }

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

    private ResponseEntity<byte[]> route(HttpServletRequest request, String url, RouterDelegateExtractor extractorCallBack) throws IOException, URISyntaxException {
        String method = request.getMethod();
        HttpMethod httpMethod = HttpMethod.resolve(method);
        HttpHeaders headers = parseRequestHeader(request, extractorCallBack);
        byte[] body = parseRequestBody(request, extractorCallBack);
        // 设置请求实体
        HttpEntity<byte[]> httpEntity = new HttpEntity<>(body, headers);
        URI uri = new URI(url);
        return restTemplate.exchange(uri, httpMethod, httpEntity, byte[].class);
    }

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

    /**
     * 处理请求头
     *
     * @param request
     * @param extractorCallBack
     * @return
     */
    private HttpHeaders parseRequestHeader(HttpServletRequest request, RouterDelegateExtractor extractorCallBack) {
        List<String> headerNames = Collections.list(request.getHeaderNames());
        HttpHeaders headers = new HttpHeaders();
        for (String headerName : headerNames) {
            List<String> headerValues = Collections.list(request.getHeaders(headerName));
            for (String headerValue : headerValues) {
                headers.add(headerName, headerValue);
            }
        }

        if (extractorCallBack != null) {
            extractorCallBack.parseRequestHeader(request, headers);
        }

        // 移除请求头accept-encoding, 不移除会导致响应体转成String时会乱码
        headers.remove("accept-encoding");
        return headers;
    }

    /**
     * 处理请求体
     *
     * @param request
     * @param extractorCallBack
     * @return
     * @throws IOException
     */
    private byte[] parseRequestBody(HttpServletRequest request, RouterDelegateExtractor extractorCallBack) throws IOException {
        // 拓展器不为null, 并且返回的结果也不为null才使用返回结果, 否则使用通用处理逻辑
        if (extractorCallBack != null) {
            byte[] body = extractorCallBack.parseRequestBody(request);
            if (body != null) return body;
        }

        InputStream inputStream = request.getInputStream();
        return StreamUtils.copyToByteArray(inputStream);
    }

}

3、暴露接口

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import zsoft.gov.datacenter.biztable.common.router.RouterDelegate;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 路由代理接口
 */
@RestController
@RequestMapping
public class RouterDelegateController {

    public final static String DELEGATE_PREFIX = "/delegate";

    @Autowired
    private RouterDelegate routerDelegate;

    /**
     * 路由代理接口
     *
     * @param serviceName
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = DELEGATE_PREFIX + "/{serviceName}/**", method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE})
    public ResponseEntity redirect(@PathVariable("serviceName") String serviceName, HttpServletRequest request, HttpServletResponse response) {
        return routerDelegate.redirect(request, response, DELEGATE_PREFIX, serviceName);
    }

}


4、基础配置

#路由代理配置-网关地址
delegate.config.api.rootPath=http://192.168.50.43:7612
#路由代理配置-服务名称, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
delegate.config.api.serviceName=common,csdn
#路由代理配置-服务网关, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
delegate.config.api.serviceRoot=default,https://csdn.net
#路由代理配置-服务拓展器, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
delegate.config.api.serviceExtractor=default,csdnBlog
  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,以下是使用 Spring Boot 实现一个 Nginx 反向代理的类: ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class ReverseProxyApplication { public static void main(String[] args) { SpringApplication.run(ReverseProxyApplication.class, args); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); } } ``` 这个类使用了 Spring Boot 框架,其中包含了一个 `RestTemplate` 的 Bean,用于发送 HTTP 请求。在实际使用中,我们可以通过注入 `RestTemplate` 来发送请求。 接下来,我们需要编写一个 Controller 类,用于接收请求并将其转发到目标服务器: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class ReverseProxyController { @Autowired private RestTemplate restTemplate; @GetMapping("/{path}") public ResponseEntity<String> proxy(@PathVariable String path) { String url = "http://example.com/" + path; ResponseEntity<String> response = restTemplate.getForEntity(url, String.class); return response; } } ``` 这个 Controller 类中包含了一个 `RestTemplate` 的实例,用于发送 HTTP 请求。在 `proxy` 方法中,我们将请求路径拼接到目标服务器的 URL 上,并使用 `RestTemplate` 发送 GET 请求。最后,我们将目标服务器的响应返回给客户端。 以上就是使用 Spring Boot 实现一个 Nginx 反向代理的类的全部内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值