How does spring cloud gateway dynamically add request parameters?

Time:2022-5-3

Background introduction

The technology stack used in the project is spring cloud. There are two functional requirements:

In terms of business, the service of spring cloud gateway module has been able to obtain the token, and the identity information has been obtained from the token after authentication;

Now I want to fill the identity information into the request parameter (here, multiple data are encapsulated into a basedto object for extension).

The microservice module for subsequent processing of specific business passes parameters in the method of the controller layer. As long as it inherits the basedto object, it can directly obtain the identity information for business logic processing.

Problem description

Simply put, the problem is how spring cloud gateway dynamically adds request parameters.

Spring Cloud Gateway Add Request Parameter

  1. Check the official documents and provide the following examples:

docs.spring.io/spring-clou…

How does spring cloud gateway dynamically add request parameters?

image.png

However, it is stated in the configuration file. It seems that it can only be a fixed value.

  1. I saw someone ask similar questions on GitHub,

github.com/spring-clou…

How does spring cloud gateway dynamically add request parameters?

image.png

But the effect is similar to that of the configuration file.

  1. Similar answers were found on stackoverflow:

stackoverflow.com/questions/6…

How does spring cloud gateway dynamically add request parameters?

Maybe the idea has a direction.

Solution

These two classes addrequestparametergatewayfilterfactory and modifyrequestbodygatewayfilterfactory are found on the spring cloud gateway source code

How does spring cloud gateway dynamically add request parameters?

image.png

The code is as follows:

AddRequestParameterGatewayFilterFactory

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.cloud.gateway.filter.factory;

import java.net.URI;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory.NameValueConfig;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;

public class AddRequestParameterGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
    public AddRequestParameterGatewayFilterFactory() {
    }

    public GatewayFilter apply(NameValueConfig config) {
        return (exchange, chain) -> {
            URI uri = exchange.getRequest().getURI();
            StringBuilder query = new StringBuilder();
            String originalQuery = uri.getRawQuery();
            if (StringUtils.hasText(originalQuery)) {
                query.append(originalQuery);
                if (originalQuery.charAt(originalQuery.length() - 1) != '&') {
                    query.append('&');
                }
            }

            query.append(config.getName());
            query.append('=');
            query.append(config.getValue());

            try {
                URI newUri = UriComponentsBuilder.fromUri(uri).replaceQuery(query.toString()).build(true).toUri();
                ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();
                return chain.filter(exchange.mutate().request(request).build());
            } catch (RuntimeException var8) {
                throw new IllegalStateException("Invalid URI query: \"" + query.toString() + "\"");
            }
        };
    }
}

Copy code

ModifyRequestBodyGatewayFilterFactory

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.cloud.gateway.filter.factory.rewrite;

import java.util.Map;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.DefaultServerRequest;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class ModifyRequestBodyGatewayFilterFactory extends AbstractGatewayFilterFactory<ModifyRequestBodyGatewayFilterFactory.Config> {
    public ModifyRequestBodyGatewayFilterFactory() {
        super(ModifyRequestBodyGatewayFilterFactory.Config.class);
    }

    /** @deprecated */
    @Deprecated
    public ModifyRequestBodyGatewayFilterFactory(ServerCodecConfigurer codecConfigurer) {
        this();
    }

    public GatewayFilter apply(ModifyRequestBodyGatewayFilterFactory.Config config) {
        return (exchange, chain) -> {
            Class inClass = config.getInClass();
            ServerRequest serverRequest = new DefaultServerRequest(exchange);
            Mono<?> modifiedBody = serverRequest.bodyToMono(inClass).flatMap((o) -> {
                return config.rewriteFunction.apply(exchange, o);
            });
            BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, config.getOutClass());
            CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, exchange.getRequest().getHeaders());
            return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
                ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                    public HttpHeaders getHeaders() {
                        HttpHeaders httpHeaders = new HttpHeaders();
                        httpHeaders.putAll(super.getHeaders());
                        httpHeaders.set("Transfer-Encoding", "chunked");
                        return httpHeaders;
                    }

                    public Flux<DataBuffer> getBody() {
                        return outputMessage.getBody();
                    }
                };
                return chain.filter(exchange.mutate().request(decorator).build());
            }));
        };
    }

    public static class Config {
        private Class inClass;
        private Class outClass;
        private Map<String, Object> inHints;
        private Map<String, Object> outHints;
        private RewriteFunction rewriteFunction;

        public Config() {
        }

        public Class getInClass() {
            return this.inClass;
        }

        public ModifyRequestBodyGatewayFilterFactory.Config setInClass(Class inClass) {
            this.inClass = inClass;
            return this;
        }

        public Class getOutClass() {
            return this.outClass;
        }

        public ModifyRequestBodyGatewayFilterFactory.Config setOutClass(Class outClass) {
            this.outClass = outClass;
            return this;
        }

        public Map<String, Object> getInHints() {
            return this.inHints;
        }

        public ModifyRequestBodyGatewayFilterFactory.Config setInHints(Map<String, Object> inHints) {
            this.inHints = inHints;
            return this;
        }

        public Map<String, Object> getOutHints() {
            return this.outHints;
        }

        public ModifyRequestBodyGatewayFilterFactory.Config setOutHints(Map<String, Object> outHints) {
            this.outHints = outHints;
            return this;
        }

        public RewriteFunction getRewriteFunction() {
            return this.rewriteFunction;
        }

        public <T, R> ModifyRequestBodyGatewayFilterFactory.Config setRewriteFunction(Class<T> inClass, Class<R> outClass, RewriteFunction<T, R> rewriteFunction) {
            this.setInClass(inClass);
            this.setOutClass(outClass);
            this.setRewriteFunction(rewriteFunction);
            return this;
        }

        public ModifyRequestBodyGatewayFilterFactory.Config setRewriteFunction(RewriteFunction rewriteFunction) {
            this.rewriteFunction = rewriteFunction;
            return this;
        }
    }
}

Copy code

In fact, it can be used as an official reference example.

According to similar contents, we can draw gourds as usual and realize the function of adding parameters on our own gateway filter.

Implementation code

Main processing flow of authentication filter

@Component
public class AuthFilter implements GlobalFilter, Ordered {

    private static final Logger LOGGER = LoggerFactory.getLogger(AuthFilter.class);

    private static AntPathMatcher antPathMatcher;

    static {
        antPathMatcher = new AntPathMatcher();
    }

    @Override
    public int getOrder() {
        return FilterOrderConstant.getOrder(this.getClass().getName());
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        URI uri = request.getURI();
        String url = uri.getPath();
        String host = uri.getHost();

        //Skip paths that do not require validation
        Stream<String> skipAuthUrls = UrlConstant.skipAuthUrls.stream();
        if(skipAuthUrls.anyMatch(path -> antPathMatcher.match(path, url))){
            //Direct return
            ServerHttpRequest.Builder builder = request.mutate();
            return chain.filter(exchange.mutate().request(builder.build()).build());
        }
        //Take the token from the request header
        String token = request.getHeaders().getFirst("Authorization");

        //Take out the identity information contained in the token
        //The verification token logic is no longer described
        BaseDTO baseDTO = getClaim(token);
        if(null == baseDTO){
        //Authentication failed and identity information could not be obtained
            return illegalResponse(exchange, "{\"code\": \"401\",\"msg\": \"unauthorized.\"}");
        }

        //Add the current identity information to the current request
        ServerHttpRequest.Builder builder = request.mutate();

        Stream<String> addRequestParameterUrls = UrlConstant.addRequestParameterUrls.stream();
        if (addRequestParameterUrls.anyMatch(path -> antPathMatcher.match(path, url))){
            //Request parameters need to be added
            if(request.getMethod() == HttpMethod.GET){
                //Get request processing parameters
                return addParameterForGetMethod(exchange, chain, uri, baseDTO, builder);
            }

            if(request.getMethod() == HttpMethod.POST){
                //Post request processing parameters
                MediaType contentType = request.getHeaders().getContentType();
                if(MediaType.APPLICATION_JSON.equals(contentType)
                        || MediaType.APPLICATION_JSON_UTF8.equals(contentType)){
                    //The request content is application JSON
                    return addParameterForPostMethod(exchange, chain, baseDTO);
                }

                if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
                    //The request content is form data
                    return addParameterForFormData(exchange, chain, baseDTO, builder);
                }

            }

            if(request.getMethod() == HttpMethod.PUT){
                //Put request processing parameters
                //Follow the post request process
                return addParameterForPostMethod(exchange, chain, baseDTO);
            }

            if(request.getMethod() == HttpMethod.DELETE){
                //Delete request processing parameters
                //Go through the get request process
                return addParameterForGetMethod(exchange, chain, uri, baseDTO, builder);
            }

        }

        //End of current filter execution
        return chain.filter(exchange.mutate().request(builder.build()).build());
    }

}
Copy code

Get request to add parameters

/**
     *Get request, adding parameters
     * @param exchange
     * @param chain
     * @param uri
     * @param baseDTO
     * @param builder
     * @return
     */
    private Mono<Void> addParameterForGetMethod(ServerWebExchange exchange, GatewayFilterChain chain, URI uri, BaseDTO baseDTO, ServerHttpRequest.Builder builder) {
        StringBuilder query = new StringBuilder();

        String originalQuery = uri.getQuery();
        if (StringUtils.hasText(originalQuery)) {
            query.append(originalQuery);
            if (originalQuery.charAt(originalQuery.length() - 1) != '&') {
                query.append('&');
            }
        }

        query.append("userId").append("=").append(baseDTO.getUserId())
                .append("&").append("userName").append("=").append(baseDTO.getUserName())
        ;

        try {
            URI newUri = UriComponentsBuilder.fromUri(uri).replaceQuery(query.toString()).build().encode().toUri();
            ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();
            return chain.filter(exchange.mutate().request(request).build());
        } catch (Exception e) {
            LOGGER.error("Invalid URI query: " + query.toString(), e);
            //End of current filter execution
            return chain.filter(exchange.mutate().request(builder.build()).build());
        }
    }
Copy code

Post requests adding parameters

The request content is application JSON

/**
     *Post request, adding parameters
     * @param exchange
     * @param chain
     * @param baseDTO
     * @return
     */
    private Mono<Void> addParameterForPostMethod(ServerWebExchange exchange, GatewayFilterChain chain, BaseDTO baseDTO) {
        ServerRequest serverRequest = new DefaultServerRequest(exchange);
        AtomicBoolean flag = new AtomicBoolean(false);
        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap((o) -> {
            if(o.startsWith("[")){
                //The body content is an array and is returned directly
                return Mono.just(o);
            }

            ObjectMapper objectMapper = new ObjectMapper();
            try {
                Map map = objectMapper.readValue(o, Map.class);

                map.put("userId", baseDTO.getUserId());
                map.put("userName", baseDTO.getUserName());

                String json = objectMapper.writeValueAsString(map);
                LOGGER.info("addParameterForPostMethod -> json = {}", json);
                return Mono.just(json);
            }catch (Exception e){
                e.printStackTrace();
                return Mono.just(o);
            }
        });

        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, exchange.getRequest().getHeaders());
        return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
            ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                public HttpHeaders getHeaders() {
                    HttpHeaders httpHeaders = new HttpHeaders();
                    httpHeaders.putAll(super.getHeaders());
                    httpHeaders.set("Transfer-Encoding", "chunked");
                    return httpHeaders;
                }

                public Flux<DataBuffer> getBody() {
                    return outputMessage.getBody();
                }
            };
            return chain.filter(exchange.mutate().request(decorator).build());
        }));
    }
Copy code

The request content is form data

/**
     *Post request, add parameters to form data
     * @param exchange
     * @param chain
     * @param baseDTO
     * @param builder
     * @return
     */
    private Mono<Void> addParameterForFormData(ServerWebExchange exchange, GatewayFilterChain chain, BaseDTO baseDTO, ServerHttpRequest.Builder builder) {
        builder.header("userId", String.valueOf(baseDTO.getUserId()));
        try {
            builder.header("userName", URLEncoder.encode(String.valueOf(baseDTO.getUserName()), "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            builder.header("userName", String.valueOf(baseDTO.getUserName()));
        }
        ServerHttpRequest serverHttpRequest = builder.build();
        HttpHeaders headers = serverHttpRequest.getHeaders();

        return chain.filter(exchange.mutate().request(serverHttpRequest).build());
    }
Copy code

Return data processing

/**
     *Return message
     * @param exchange
     * @param data
     * @return
     */
    private Mono<Void> illegalResponse(ServerWebExchange exchange, String data) {
        ServerHttpResponse originalResponse = exchange.getResponse();
        originalResponse.setStatusCode(HttpStatus.OK);
        originalResponse.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
        byte[] response = data.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = originalResponse.bufferFactory().wrap(response);
        return originalResponse.writeWith(Flux.just(buffer));
    }
Copy code

Final effect

As described above, the userid and username attributes have been written into the request parameter.

In the service module of specific business processing, as long as the controller layer inherits the basedto class containing userid and username attributes, this information can be obtained for the actual business process.

If you want to know more about your friends, scan the QR code below to follow the official account "W's programming diary" for more free information
! [wechat picture _20211124131235. JPG]( https://upload-images.jianshu.io/upload_images/27369920-74ced392da13af72.jpg?imageMogr2/auto -orient/strip%7CimageView2/2/w/1240)