Soul Gateway learning (2-3)Http client access source code parsing

Analysis of the REGISTRATION logic for HTTP User access to the Soul Gateway

1. Registration entry

When an HTTP user accesses the Soul gateway, it calls the soul-admin interface and registers the interface that needs to be managed by the Soul gateway.

The interface information is as follows:

// SpringMvcClientBeanPostProcessor.java
/**
 * Instantiates a new Soul client bean post processor.
 *
 * @param soulSpringMvcConfig the soul spring mvc config
 */
public SpringMvcClientBeanPostProcessor(final SoulSpringMvcConfig soulSpringMvcConfig) {
    ValidateUtils.validate(soulSpringMvcConfig);
    this.soulSpringMvcConfig = soulSpringMvcConfig;
    url = soulSpringMvcConfig.getAdminUrl() + "/soul-client/springmvc-register";
    executorService = new ThreadPoolExecutor(1.1.0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
}
Copy the code

2. Springmvc-register interface logic

Search globally for “springMVC-Register” and find the SoulClientController under the soul-admin module. Ha ha ~

// SoulClientController.java
/**
 * Register spring mvc string.
 *
 * @param springMvcRegisterDTO the spring mvc register dto
 * @return the string
 */
@PostMapping("/springmvc-register")
public String registerSpringMvc(@RequestBody final SpringMvcRegisterDTO springMvcRegisterDTO) {
    return soulClientRegisterService.registerSpringMvc(springMvcRegisterDTO);
}
Copy the code

Service layer implementation class:

// SoulClientRegisterServiceImpl.java
@Override
@Transactional
public String registerSpringMvc(final SpringMvcRegisterDTO dto) {
    if (dto.isRegisterMetaData()) {
        MetaDataDO exist = metaDataMapper.findByPath(dto.getPath());
        if (Objects.isNull(exist)) {
            saveSpringMvcMetaData(dto);
        }
    }
    String selectorId = handlerSpringMvcSelector(dto);
    handlerSpringMvcRule(selectorId, dto);
    return SoulResultMessage.SUCCESS;
}
Copy the code

Dto.isregistermetadata () specifies whether or not to register metadata information.

2.1 First look at the method handlerSpring mvSelector, which handles the Selector.

// SoulClientRegisterServiceImpl.java
private String handlerSpringMvcSelector(final SpringMvcRegisterDTO dto) {
    String contextPath = dto.getContext();
    // contextPath = contextPath; // contextPath = contextPath;
    SelectorDO selectorDO = selectorService.findByName(contextPath);
    String selectorId;
    String uri = String.join(":", dto.getHost(), String.valueOf(dto.getPort()));
    if (Objects.isNull(selectorDO)) {
        // Not yet registered
        selectorId = registerSelector(contextPath, dto.getRpcType(), dto.getAppName(), uri);
    } else {
        // It has been registered, and the business system will arrive here when it restarts
        selectorId = selectorDO.getId();
        //update upstream
        String handle = selectorDO.getHandle();
        String handleAdd;
        DivideUpstream addDivideUpstream = buildDivideUpstream(uri);
        SelectorData selectorData = selectorService.buildByName(contextPath);
        if (StringUtils.isBlank(handle)) {
            handleAdd = GsonUtils.getInstance().toJson(Collections.singletonList(addDivideUpstream));
        } else {
            List<DivideUpstream> exist = GsonUtils.getInstance().fromList(handle, DivideUpstream.class);
            for (DivideUpstream upstream : exist) {
                if (upstream.getUpstreamUrl().equals(addDivideUpstream.getUpstreamUrl())) {
                    return selectorId;
                }
            }
            exist.add(addDivideUpstream);
            handleAdd = GsonUtils.getInstance().toJson(exist);
        }
        selectorDO.setHandle(handleAdd);
        selectorData.setHandle(handleAdd);
        // update db
        selectorMapper.updateSelective(selectorDO);
        // submit upstreamCheck
        upstreamCheckService.submit(contextPath, addDivideUpstream);
        // publish change event.
        eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, DataEventTypeEnum.UPDATE,
                Collections.singletonList(selectorData)));
    }
    return selectorId;
}
Copy the code

2.1.1 Connecting to the Soul Gateway for the first time

If you’re new to it, you’re not going to find a selectorDO in your database, so if you go to the registerSelector method, look carefully at which database tables you’re inserting data into.

// SoulClientRegisterServiceImpl.java
private String registerSelector(final String contextPath, final String rpcType, final String appName, final String uri) {
    SelectorDTO selectorDTO = SelectorDTO.builder()
            .name(contextPath)
            .type(SelectorTypeEnum.CUSTOM_FLOW.getCode())
            .matchMode(MatchModeEnum.AND.getCode())
            .enabled(Boolean.TRUE)
            .loged(Boolean.TRUE)
            .continued(Boolean.TRUE)
            .sort(1)
            .build();
    if (RpcTypeEnum.DUBBO.getName().equals(rpcType)) {
        selectorDTO.setPluginId(getPluginId(PluginEnum.DUBBO.getName()));
    } else if (RpcTypeEnum.SPRING_CLOUD.getName().equals(rpcType)) {
        selectorDTO.setPluginId(getPluginId(PluginEnum.SPRING_CLOUD.getName()));
        selectorDTO.setHandle(GsonUtils.getInstance().toJson(buildSpringCloudSelectorHandle(appName)));
    } else if (RpcTypeEnum.SOFA.getName().equals(rpcType)) {
        selectorDTO.setPluginId(getPluginId(PluginEnum.SOFA.getName()));
        selectorDTO.setHandle(appName);
    } else if (RpcTypeEnum.TARS.getName().equals(rpcType)) {
        selectorDTO.setPluginId(getPluginId(PluginEnum.TARS.getName()));
        selectorDTO.setHandle(appName);
    } else {
        //is divide
        DivideUpstream divideUpstream = buildDivideUpstream(uri);
        String handler = GsonUtils.getInstance().toJson(Collections.singletonList(divideUpstream));
        selectorDTO.setHandle(handler);
        selectorDTO.setPluginId(getPluginId(PluginEnum.DIVIDE.getName()));
        upstreamCheckService.submit(selectorDTO.getName(), divideUpstream);
    }
    SelectorConditionDTO selectorConditionDTO = new SelectorConditionDTO();
    selectorConditionDTO.setParamType(ParamTypeEnum.URI.getName());
    selectorConditionDTO.setParamName("/");
    selectorConditionDTO.setOperator(OperatorEnum.MATCH.getAlias());
    selectorConditionDTO.setParamValue(contextPath + "/ * *");
    selectorDTO.setSelectorConditions(Collections.singletonList(selectorConditionDTO));
    return selectorService.register(selectorDTO);
}
Copy the code

If you’re excited to see all these if else’s, you can think about how you can optimize out all these if else’s, and get the PR ^ – ^.

Selectorservice.register (SelectorDTO) : selectorService.register(SelectorDTO) : SelectorService.register (SelectorDTO) : SelectorService.register (SelectorDTO) : SelectorService.register (SelectorDTO)

// SelectorServiceImpl.java
@Override
public String register(final SelectorDTO selectorDTO) {
    SelectorDO selectorDO = SelectorDO.buildSelectorDO(selectorDTO);
    List<SelectorConditionDTO> selectorConditionDTOs = selectorDTO.getSelectorConditions();
    if (StringUtils.isEmpty(selectorDTO.getId())) {
        selectorMapper.insertSelective(selectorDO);
        selectorConditionDTOs.forEach(selectorConditionDTO -> {
            selectorConditionDTO.setSelectorId(selectorDO.getId());
            // The dao layer is used to insert data in the for loop.
            selectorConditionMapper.insertSelective(SelectorConditionDO
                    .buildSelectorConditionDO(selectorConditionDTO));
        });
    }
    publishEvent(selectorDO, selectorConditionDTOs);
    return selectorDO.getId();
}
Copy the code

You can see that there are two inbound methods that insert data into the selector and selector_condition tables. Here we do not specifically investigate the table structure and business significance, the following up.

PublishEvent method, involves ApplicationEventPublisher interface, it is an implementation of the observer pattern, after publishing events through the listener to complete the follow-up operation, here don’t press the table first, and later wrote an article in the analysis.

2.1.2 The Soul gateway has been connected

Just like Inception, we rewind to the second level of the dream and go back to another branch of the insertion data, which, presumably, reboots the system already connected to the Soul gateway, or starts the logic of a new node.

Post the previous code again:

// SoulClientRegisterServiceImpl.java
private String handlerSpringMvcSelector(final SpringMvcRegisterDTO dto) {
    String contextPath = dto.getContext();
    // contextPath = contextPath; // contextPath = contextPath;
    SelectorDO selectorDO = selectorService.findByName(contextPath);
    String selectorId;
    String uri = String.join(":", dto.getHost(), String.valueOf(dto.getPort()));
    if (Objects.isNull(selectorDO)) {
        // Not yet registered
        selectorId = registerSelector(contextPath, dto.getRpcType(), dto.getAppName(), uri);
    } else {
        // If the connected service system is restarted or a new node is started, the node will be displayed
        selectorId = selectorDO.getId();
        //update upstream
        // The Handle field stores the actual node information of this interface. Multiple machines may require load balancing
        String handle = selectorDO.getHandle();
        String handleAdd;
        DivideUpstream addDivideUpstream = buildDivideUpstream(uri);
        SelectorData selectorData = selectorService.buildByName(contextPath);
        if (StringUtils.isBlank(handle)) {
            // This interface has been registered before, but will enter when the first server node is connected to Soul
            handleAdd = GsonUtils.getInstance().toJson(Collections.singletonList(addDivideUpstream));
        } else {
            // If at least one server node has been connected, it will enter here to check whether it is the same node (use upstreamUrl to distinguish), if the same node directly returns
            List<DivideUpstream> exist = GsonUtils.getInstance().fromList(handle, DivideUpstream.class);
            for (DivideUpstream upstream : exist) {
                if (upstream.getUpstreamUrl().equals(addDivideUpstream.getUpstreamUrl())) {
                    returnselectorId; }}// If it is not the same node, add the new node to the Handle field
            exist.add(addDivideUpstream);
            handleAdd = GsonUtils.getInstance().toJson(exist);
        }
        selectorDO.setHandle(handleAdd);
        selectorData.setHandle(handleAdd);
        // update db Updates the database
        selectorMapper.updateSelective(selectorDO);
        // submit upstreamCheck
        upstreamCheckService.submit(contextPath, addDivideUpstream);
        // publish change event.
        eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, DataEventTypeEnum.UPDATE,
                Collections.singletonList(selectorData)));
    }
    return selectorId;
}
Copy the code

As we have not studied the design of database table structure, according to the known part of the guess, 1 selector corresponds to a divide plug-in, which is identified by contextPath (in this case, “/ HTTP “). A single contextPath can deploy multiple server nodes. The node information is stored in the HANDLE field in JSON form.

// Handle /handleAdd data format[{"upstreamHost": "localhost"."protocol": "http://"."upstreamUrl": "10.0.0.12:8188"."weight": 50."status": true."timestamp": 0."warmup": 0}]Copy the code

Next, update the database updateSelective.

upstreamCheckService.submit(contextPath, addDivideUpstream); The real server node information is cached in a Map(UPSTREAM_MAP), and a scheduled task is performed periodically to check if the server node is down, which is removed to prevent requests from being sent to the node that is down.

Then eventPublisher. PublishEvent (), like publishEvent method on the surface of the front, after publishing events through the listener do follow-up operations (a simple introduction, Soul gateway: SelectorData: SelectorData: SelectorData: SelectorData: SelectorData: SelectorData: SelectorData: SelectorData: SelectorData

So we’re finally done analyzing handlerSpring MV Selector.

2.2 Let’s look at the method handlerSpringMvcRule, which handles the Rule.

// SoulClientRegisterServiceImpl.java
private void handlerSpringMvcRule(final String selectorId, final SpringMvcRegisterDTO dto) {
    RuleDO ruleDO = ruleMapper.findByName(dto.getRuleName());
    if(Objects.isNull(ruleDO)) { registerRule(selectorId, dto.getPath(), dto.getRpcType(), dto.getRuleName()); }}Copy the code

First, take the name of the rule, to the table to retrieve data, if the table name has been registered, no operation.

Look at the database data, which is the address of the interface under the business system.

mysql> use soul;
Database changed

mysql> select * from rule where name = '/http/order/findById' \G
*************************** 1. row ***************************
          id: 1349650371868782592
 selector_id: 1349650371302551552
  match_mode: 0
        name: /http/order/findById
     enabled: 1
       loged: 1
        sort: 1
      handle: {"loadBalance":"random"."retry": 0."timeout":3000}
date_created: 2021-01-14 17:31:39
date_updated: 2021-01-14 17:31:39
1 row in set (0.00 sec)
Copy the code

If no data is retrieved, register the rule.

// SoulClientRegisterServiceImpl.java
private void registerRule(final String selectorId, final String path, final String rpcType, final String ruleName) {
    RuleHandle ruleHandle = RuleHandleFactory.ruleHandle(RpcTypeEnum.acquireByName(rpcType), path);
    RuleDTO ruleDTO = RuleDTO.builder()
            .selectorId(selectorId)
            .name(ruleName)
            .matchMode(MatchModeEnum.AND.getCode())
            .enabled(Boolean.TRUE)
            .loged(Boolean.TRUE)
            .sort(1)
            .handle(ruleHandle.toJson())
            .build();
    RuleConditionDTO ruleConditionDTO = RuleConditionDTO.builder()
            .paramType(ParamTypeEnum.URI.getName())
            .paramName("/")
            .paramValue(path)
            .build();
    if (path.indexOf("*") > 1) {
        ruleConditionDTO.setOperator(OperatorEnum.MATCH.getAlias());
    } else {
        ruleConditionDTO.setOperator(OperatorEnum.EQ.getAlias());
    }
    ruleDTO.setRuleConditions(Collections.singletonList(ruleConditionDTO));
    ruleService.register(ruleDTO);
}
Copy the code

The first line obtains the corresponding RuleHandle according to the rpcType(” HTTP “). Here, there are three built-in types by default. The one we have here is HTTP, which corresponds to the DivideRuleHandle.

// RuleHandleFactory.java
public final class RuleHandleFactory {

    /** * The RpcType to RuleHandle class map. */
    private static final Map<RpcTypeEnum, Class<? extends RuleHandle>> RPC_TYPE_TO_RULE_HANDLE_CLASS = new ConcurrentHashMap<>();

    /** * The default RuleHandle. */
    private static final Class<? extends RuleHandle> DEFAULT_RULE_HANDLE = SpringCloudRuleHandle.class;

    static {
        RPC_TYPE_TO_RULE_HANDLE_CLASS.put(RpcTypeEnum.HTTP, DivideRuleHandle.class);
        RPC_TYPE_TO_RULE_HANDLE_CLASS.put(RpcTypeEnum.DUBBO, DubboRuleHandle.class);
        RPC_TYPE_TO_RULE_HANDLE_CLASS.put(RpcTypeEnum.SOFA, SofaRuleHandle.class);
    }

    /**
     * Get a RuleHandle object with given rpc type and path.
     * @param rpcType   rpc type.
     * @param path      path.
     * @return          RuleHandle object.
     */
    public static RuleHandle ruleHandle(final RpcTypeEnum rpcType, final String path) {
        if (Objects.isNull(rpcType)) {
            return null;
        }
        Class<? extends RuleHandle> clazz = RPC_TYPE_TO_RULE_HANDLE_CLASS.getOrDefault(rpcType, DEFAULT_RULE_HANDLE);
        try {
            return clazz.newInstance().createDefault(path);
        } catch (InstantiationException | IllegalAccessException e) {
            throw new SoulException(
                    String.format("Init RuleHandle failed with rpc type: %s, rule class: %s, exception: %s", rpcType, clazz.getSimpleName(), e.getMessage())); }}}Copy the code

The RuleDTO object is constructed and the rules are registered.

// RuleServiceImpl.java
@Override
public String register(final RuleDTO ruleDTO) {
    RuleDO ruleDO = RuleDO.buildRuleDO(ruleDTO);
    List<RuleConditionDTO> ruleConditions = ruleDTO.getRuleConditions();
    if (StringUtils.isEmpty(ruleDTO.getId())) {
        ruleMapper.insertSelective(ruleDO);
        ruleConditions.forEach(ruleConditionDTO -> {
            ruleConditionDTO.setRuleId(ruleDO.getId());
            // The dao layer is used to insert data in the for loop.
            ruleConditionMapper.insertSelective(RuleConditionDO
                    .buildRuleConditionDO(ruleConditionDTO));
        });
    }
    publishEvent(ruleDO, ruleConditions);
    return ruleDO.getId();
}
Copy the code

Insert data into the RULE and RULE_condition tables, respectively.

The publishEvent() method sends RuleData data to the Soul gateway over a websocket long connection.

3. Summary

/soul-client/ springmVC-register /soul-client/ springmVC-register

  • With the selector
    • Add or modify selector, selector_condition data, persist to MySQL.
    • Send data changes to the Soul gateway through websocket.
  • Processing rule
    • Add or modify the rule and RUle_condition table data, persist to MySQL.
    • Send data changes to the Soul gateway through websocket.

The table structure and field meaning need further study and research. After websocket is sent to the Soul gateway, what does the gateway do needs further analysis.

At this point, the HTTP user access Soul gateway registration logic is analyzed.

If you have the need to use gateway in your work, or have the pursuit of learning gateway personally, welcome to analyze and learn with me, Soul gateway, you deserve it.