Build a spring cloud gateway from scratch (1)

Time:2020-3-19

New spring boot project

How to build a new spring boot project will not be described here in detail. You can not browse previous blogs or Baidu directly. The corresponding POM file is posted here.

POM depends on the following:

4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.2.5.RELEASE
         
    
    com.lifengdi
    gateway
    0.0.1-SNAPSHOT
    gateway
    Demo project for Spring Boot

    
        1.8
        Hoxton.SR3
    

    
        
            org.springframework.cloud
            spring-cloud-starter-gateway
        

        
            org.projectlombok
            lombok
            1.18.12
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        

        
            org.springframework.cloud
            spring-cloud-starter-netflix-hystrix
        

        
            org.springframework.boot
            spring-boot-starter-actuator
        



        
            org.apache.commons
            commons-pool2
        

        
            commons-io
            commons-io
            2.5
            compile
        

        
            com.alibaba
            fastjson
            1.2.58
        

        
            org.springframework.boot
            spring-boot-starter-mail
        
    

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin

Because it’s a gateway project, you don’t need tospring-boot-starter-webRelated dependencies.

The configuration file is as follows:

server:
  port: 8080
spring:
  application:
    name: spring-cloud-gateway-demo
  cloud:
    gateway:
      discovery:
        locator:
          Enabled: true enable route access
      routes:
        - id: path_route
          #Specify domain name
          uri: http://localhost:8081
          predicates:
            - Path=/jar/**
          filters:
            #Fuse configuration
            - name: Hystrix
              args:
                name: default
                fallbackUri: forward:/fallback
        - id: path_route2
          #Specify domain name
          uri: http://localhost:8082
          predicates:
            - Path=/war/**
          filters:
            #Fuse configuration
            - name: Hystrix
              args:
                name: hystrix1
                fallbackUri: forward:/fallback

  mvc:
    throw-exception-if-no-handler-found: true

#Default fusing timeout 30s
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000
    hystrix1:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1000

Fusing (interface or project)

The jar package related to fusing is as follows:

org.springframework.cloud
    spring-cloud-starter-netflix-hystrix

Default fuse callback interface:

package com.lifengdi.gateway.hystrix;

import com.lifengdi.gateway.exception.BaseException;
import com.lifengdi.gateway.response.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author: Li Fengdi
 * @date: 2020-03-18 16:35
 */
@RestController
@Slf4j
public class DefaultHystrixController {
    @RequestMapping("/fallback")
    public ResponseResult fallback(){

        Log.error ("trigger fuse...");
        return ResponseResult.fail(BaseException.DEFAULT_HYSTRIX.build());
    }
}

The specific configuration file is as follows:

routes:
        - id: path_route
          #Specify domain name
          uri: http://localhost:8081
          predicates:
            - Path=/jar/**
          filters:
            #Fuse configuration
            - name: Hystrix
              args:
                name: default
                fallbackUri: forward:/fallback
        - id: path_route2
          #Specify domain name
          uri: http://localhost:8082
          predicates:
            - Path=/war/**
          filters:
            #Fuse configuration
            - name: Hystrix
              args:
                name: hystrix1
                fallbackUri: forward:/fallback

  mvc:
    throw-exception-if-no-handler-found: true

#Default fusing timeout 30s
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000
    hystrix1:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1000

defaulthystrix1For user-defined parameters, multiple fuse policies can be configured. Different interfaces and services can be configured with corresponding timeout separately. No additional development is required, but additional configuration files need to be added.

Global session sharing

Dependent jar package:

org.springframework.boot
    spring-boot-starter-data-redis-reactive



    org.springframework.session
    spring-session-data-redis



    org.apache.commons
    commons-pool2



    org.springframework.boot
    spring-boot-starter-security

Related YML configuration:

spring:
  redis:
    database: 0
    host: localhost
    port: 6379
    password: 123456
    lettuce:
      pool:
        max-active: 300
        max-idle: 8
        max-wait: -1ms
        min-idle: 0
  session:
    store-type: redis

spring.session.store-typeSpring is implemented by redis by default, but there are other configurations.

Add the code as follows:

Permission is related. All are released by default:

package com.lifengdi.gateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@Configuration
@EnableWebFluxSecurity
public class GatewaySecurityConfig {
    @Bean
    SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity serverHttpSecurity)
            throws Exception {
        serverHttpSecurity
                .csrf().disable()
                .authorizeExchange().pathMatchers("/**").permitAll()
                .anyExchange()
                .authenticated();
        return serverHttpSecurity.build();
    }
}

Session related:

package com.lifengdi.gateway.config;

import com.lifengdi.gateway.resolver.MyCookieWebSessionIdResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.ResponseCookie;
import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession;
import org.springframework.web.server.session.CookieWebSessionIdResolver;
import org.springframework.web.server.session.WebSessionIdResolver;

import java.util.function.Consumer;

@Configuration
@EnableRedisWebSession(maxInactiveIntervalInSeconds = 10*60*60, redisNamespace = "my:spring:session")
public class WebSessionConfig {

    @Bean
    public WebSessionIdResolver webSessionIdResolver() {
        CookieWebSessionIdResolver resolver = new MyCookieWebSessionIdResolver();
        resolver.setCookieName("SESSIONID");

        Consumer consumer = responseCookieBuilder -> {
            responseCookieBuilder.path("/");
        };
        resolver.addCookieInitializer(consumer);
        return resolver;
    }

}

Note that the@EnableRedisWebSessionComments, not@EnableRedisHttpSession, this is different from zuul.

When using zuul as a gateway, use@EnableRedisHttpSessionIn the configuration, session information can be shared through redis

Spring also provides@EnableRedisWebSessionTo support WebFlux.

It is worth mentioning that the internal implementation of these two annotations is not the same, and the configuration that needs to be customized is not the same.

Here you can customize the cookie name, path, etcwebSessionIdResolverTo achieve, notcookieSerializer。 If usedcookieSerializerYes, yes.@EnableRedisWebSessionIt doesn’t work. It’s been a long time before this hole!

The mycookiewebsessionidresolver code is as follows:

package com.lifengdi.gateway.resolver;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpCookie;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.session.CookieWebSessionIdResolver;

import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 *Customize the websessionid parser to be compatible with {@ link org. Springframework. Session. Data. Redis. Config. Annotation. Web. Http. Enablereredishttpsession}
 * 
 *Usebase64encoding in {@ link defaultcookieserializer} defaults to true when using enablereredishttpsession, and Base64 is used for the sessionid in the cookie
 *Encryption, but if {@ link org. Springframework. Session. Data. Redis. Config. Annotation. Web. Server. Enablererediswebsession}, the default is
 *The resolver of does not decrypt the sessionid, resulting in incorrect session
 * 
 *
 * @author: Li Fengdi
 * @date: 2020/3/16 15:41
 */
@Slf4j
public class MyCookieWebSessionIdResolver extends CookieWebSessionIdResolver {

    @Override
    public List resolveSessionIds(ServerWebExchange exchange) {
        MultiValueMap cookieMap = exchange.getRequest().getCookies();
        List cookies = cookieMap.get(getCookieName());
        if (cookies == null) {
            return Collections.emptyList();
        }
        return cookies.stream().map(HttpCookie::getValue).map(this::base64Decode).collect(Collectors.toList());
    }

    /**
     *Base64 decoding
     *
     * @param base64Value base64Value
     *@ return decoded string
     */
    private String base64Decode(String base64Value) {
        try {
            byte[] decodedCookieBytes = Base64.getDecoder().decode(base64Value);
            return new String(decodedCookieBytes);
        } catch (Exception ex) {
            log.debug("Unable to Base64 decode value: " + base64Value);
            return null;
        }
    }

}

In fact, this code is a referencecookieSerializerThe code in.

If usebase64encoding is specified as false, that is, sessionid is not encrypted, then this code is not needed.

The code has been uploaded to GIT. You can go and have a look if you need it.

Git code address: https://github.com/lifengdi/spring-cloud-gateway-demo

Original address: https://www.lifengdi.com/archives/article/1776

Recommended Today

Understanding and deepening of relative path and absolute path

What is relative path and absolute path Last week’s report solved some problems, but also exposed many problems, one of which is the relative path and absolute path. For PHP using xampp to build a server, the relative path refers to the current file relative to the user’s access, and the absolute path refers to […]