Springboot 使用拦截器完成请求重定向

背景

将HTTP请求地址重定向到请求体内传入的请求接口名。

相关依赖

完成拦截器用到的两个注解:@WebFilter、@ServletComponentScan,分别在 Tomcat 依赖包和 Springboot 依赖包中。

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.1</version>
        <relativePath/>
    </parent>
    <dependencies>
		<dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>10.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>3.1.1</version>
        </dependency>
     </dependencies>

relativePath : 查找顺序。指定获取路径,设定一个空值将始终从仓库中获取,不从本地路径获取。
spring-boot-starter-web 的依赖中已经包含了 Tomcat 依赖,直接引入 Springboot 的包即可。

实现

使用拦截器的方式实现重定向

首先因为重定向的地址存在于请求的body中,而HttpServletRequest只能读取一次body流,所以需要实现一个RequestWrapper,来保存从原来请求中读取出来的body,并且需要修改相应的获取参数和body流的方法,实现参数的多次读取。

package com.test;

import com.alibaba.fastjson2.JSON;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.io.IOUtils;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.*;

@Getter
@Setter
public class RequestWrapper extends HttpServletRequestWrapper {

    /**
     * 请求接口名参数 key
     */
    private static final String COMMAND_ID = "command_id";
    /**
     * URL分隔符
     */
    private static final String URL_SEPARATOR = "/";

    /**
     * 请求体
     */
    private String body;

    /**
     * 请求参数
     */
    private Map<String, String[]> paramMap = new HashMap<>();

    /**
     * Constructs a request object wrapping the given request.
     *
     * @param request The request to wrap
     * @throws IllegalArgumentException if the request is null
     */
    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.body = analysisBodyString(request);
        this.paramMap.putAll(request.getParameterMap());
    }

    /**
     * 从请求体中解析请求地址
     *
     * @return 请求地址
     */
    public String getRequestUrl() {
        return URL_SEPARATOR + JSON.parseObject(body).getString(COMMAND_ID);
    }

    @Override
    public String getParameter(String name) {
        String[] values = paramMap.get(name);
        return values == null || values.length < 1 ? null : values[0];
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return paramMap;
    }

    @Override
    public Enumeration<String> getParameterNames() {
        if (paramMap.isEmpty()) {
            return super.getParameterNames();
        }

        Set<String> paramNames = new LinkedHashSet<>();
        paramNames.addAll(Collections.list(super.getParameterNames()));
        paramNames.addAll(paramMap.keySet());
        return Collections.enumeration(paramNames);
    }

    @Override
    public String[] getParameterValues(String name) {
        return paramMap.get(name);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteInputStream = new ByteArrayInputStream(body.getBytes());

        // 参考 ServletInputStream 内部实现 BodyInputStream 类的方法定义
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener listener) {
                throw new UnsupportedOperationException();
            }

            @Override
            public int read() throws IOException {
                return byteInputStream.read();
            }

            @Override
            public void close() throws IOException {
                byteInputStream.close();
            }

            @Override
            public synchronized void reset() throws IOException {
                byteInputStream.reset();
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    /**
     * 解析请求参数
     *
     * @param request servletRequest
     * @return 请求体-String
     * @throws IOException
     */
    private String analysisBodyString(final HttpServletRequest request) throws IOException {
        String contentType = request.getContentType();
        String bodyString = "";
        StringBuilder sb = new StringBuilder();
        // 表单类型请求特殊处理
        boolean isForm = !contentType.isBlank() && (contentType.contains("multipart/form-data") || contentType.contains("x-www-form-urlencoded"));
        if (isForm) {
            Map<String, String[]> parameterMap = request.getParameterMap();
            for (Map.Entry<String, String[]> next : parameterMap.entrySet()) {
                String[] values = next.getValue();
                String value = null;
                if (values != null) {
                    if (values.length == 1) {
                        value = values[0];
                    } else {
                        value = Arrays.toString(values);
                    }
                }
                sb.append(next.getKey()).append("=").append(value).append("&");
            }
            if (!sb.isEmpty()) {
                bodyString = sb.substring(0, sb.toString().length() - 1);
            }
            return bodyString;
        } else {
            // 使用系统默认字符集将请求输入转换成字符串
            return IOUtils.toString(request.getInputStream(), Charset.defaultCharset());
        }
    }
}

在完成了RequestWapper的编写后,定义一个拦截器,将原有的HttpRequest请求转换为RequestWrapper,进行参数的读取。

package com.test;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;

import java.io.IOException;

@Slf4j
@WebFilter(urlPatterns = "/test")
@Order(1)
public class FrontRequestCommandFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) request);
        RequestDispatcher requestDispatcher = request.getRequestDispatcher(requestWrapper.getRequestUrl());
        requestDispatcher.forward(requestWrapper, response);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

因为使用了@WebFilter注解,还需要在Springboot启动类上面加上@ServletComponentScan注解以开启功能。

package com.test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan
public class BootstrapApplication {
    public static void main(String[] args) {
        SpringApplication.run(BootstrapApplication.class, args);
    }
}

注意事项

1、在进行消息重定向时,不能传递原来的HttpRequest,因为在解析 body 的时候已经读取了请求的输入流。HttpRequest 的body输入流只能读取一次,后续controller层使用 @RequestBody 会读取不到数据。所以直接传递包装之后的 RequestWrapper,Wrapper 中已经重写了 getInputStream 方法,能够实现 @RequestBody 注解功能。
2、在匹配HTTP请求的媒体类型时,不同的浏览器可能会有不同的值,所以不能简单的使用字符串匹配,应使用 MediaType 提供的类型转换方法: MediaType requestType = MediaType.parseMediaType(contentType)。MediaType 重写了 equals 方法,可以直接使用 equals 比较两个媒体类型是否一致。

使用 ModelAndView 实现重定向

在 controller 中直接返回携带有视图名称的 ModelAndView 实现重定向

	@RequestMapping(value = "/test", method = {RequestMethod.POST, RequestMethod.GET})
    public ModelAndView forward(HttpServletRequest request) {
        String url = request.getParameter("url");
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName(url);
        return modelAndView;
    }

参考文献

Spring MVC页面重定向
spring boot使用servletFilter实现重定向
SpringBoot项目后端重定向的问题
Springboot注解@ServletComponentScan和@ComponentScan
SpringBoot下,利用@WebFilter配置使用与注意Filter
Servlet3.0下@WebFilter注解配置Filter
HttpServletRequest获取POST请求Body参数3种方法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值