zuul网关路由

一 环境准备:

  首先好一套简单的springcloud基本框架(zhangpba-springcloud)

  公共代码:study-common  

  注册中心:study-eureka   端口:8815

  文件服务:study-file       端口:8816

  用户服务:study-user    端口:8817

  

  1 其中study-user外露两个接口,外露接口的代码:

    http://127.0.0.1:8817/client/getFile?name=名称参数【利用feign调用study-file的外露接口(getHost)】

    http://127.0.0.1:8817/client/postFile 【利用feign调用study-file的外露接口(postHost)】

package com.study.user.controller;

import com.study.vo.User;
import com.study.user.feign.FileServiceFeign;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试feign接口:fegin接口消费方
 *
 * @author zhangpba
 */
@RestController
public class FeignTestController {
    private static final Logger logger = LoggerFactory.getLogger(FeignTestController.class);

    @Autowired
    private FileServiceFeign fileServiceFeign;

    /**
     * 测试post请求:消费方
     *
     * @param user 用户参数
     * @return
     */
    @RequestMapping(value = "/client/postFile", method = RequestMethod.POST)
    public String postFile(User user) {
        return fileServiceFeign.postFileHost(user);
    }

    /**
     * 测试get请求:消费方
     *
     * @param name 参数
     * @return
     */
    @RequestMapping(value = "/client/getFile", method = RequestMethod.GET)
    public String getFile(String name) {
        return fileServiceFeign.getFileHost(name);
    }
}

  2 study-file外露两个接口,外露接口的代码:

    http://127.0.0.1:8816/getFileHost?name=名称参数

    http://127.0.0.1:8816/postFileHost?name=名称参数

package com.study.file.controller;

import com.study.vo.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

/**
 * 测试feign接口:fegin接口服务提供方
 *
 * @author zhangpba
 */
@RestController
public class FeginTestController {
    private static final Logger logger = LoggerFactory.getLogger(FeginTestController.class);

    /**
     * 测试feign接口的get请求:服务提供方
     *
     * @param name
     * @return
     */
    @RequestMapping(value = "/getFileHost", method = RequestMethod.GET)
    public String getFileHost(@RequestParam("name") String name) {
        logger.info("进入feign服务提供者:getFileHost");
        return "我是file服务get请求返回的数据:" + name;
    }

    /**
     * 测试feign接口的post请求:服务提供方
     *
     * @param user
     * @return
     */
    @RequestMapping(value = "/postFileHost", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
    public String postFileHost(@RequestBody User user) {
        logger.info("进入feign服务提供者:postFileHost");
        return "我是file服务post请求返回的数据: " + user;
    }
}

二 网关服务准备

  增加网关服务:study-zuul   端口号:8888

  搭建一个springboot服务,注册到study-eureka中,并添加zuul的包

  pom.xml

     <!--2021-06-01 zuul网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
        </dependency>
        <!--zuul网关的重试机制,不是使用ribbon内置的重试机制是借助spring-retry组件实现的重试 开启zuul网关重试机制需要增加下述依赖-->
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>

  启动类 StudyZuulApplication 

package com.study.zuul;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

/**
 * @author zhangpba
 * @EnableZuulProxy - 开启Zuul网关。
 * 当前应用是一个Zuul微服务网关。会在Eureka注册中心中注册当前服务。并发现其他的服务。
 * Zuul需要的必要依赖是spring-cloud-starter-zuul。
 */
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableZuulProxy
public class StudyZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(StudyZuulApplication.class, args);
    }
}

三 测试

  3.1 测试网关的代理功能

  配置增加,请求‘http://127.0.0.1:8888/api/user/client/getFile?name=测试zuul’,可以看到直接请求的是zuul的端口8888,但是可以请求到study-file中,说明网关代理了study-user,而study-user又调用了study-file,所以此时网关已经代理了配置文件中的study-user的外露接口

zuul:
  prefix: /api                                        # 配置请求路径前缀,所有基于此前缀的请求都由zuul网关提供代理
  routes:
    study-user-service.path: /user/**                  # 使用路径方式匹配路由规则
    study-user-service.serviceId: study-user           # 使用服务名称匹配

  study-file打印的日志如下:

2021-06-02 23:03:37.368  INFO 7312 --- [nio-8816-exec-4] c.s.file.controller.FeginTestController  : 进入feign服务提供者:getFileHost

  3.2 测试网关的路由功能

  3.2.1 配置file路由,请求‘http://127.0.0.1:8888/api/file/getFileHost?name=测试zuul’,网关可以能代理study-file的外露接口

zuul:
  prefix: /api                                        # 配置请求路径前缀,所有基于此前缀的请求都由zuul网关提供代理
  routes:
    study-user-service.path: /user/**                  # 使用路径方式匹配路由规则
    study-user-service.serviceId: study-user           # 使用服务名称匹配

    study-file-service.path: /file/**                  # 使用路径方式匹配路由规则
    study-file-service.serviceId: study-file           # 使用服务名称匹配

  study-file输出的日志如下:

2021-06-02 23:20:20.083  INFO 7312 --- [nio-8816-exec-2] c.s.file.controller.FeginTestController  : 进入feign服务提供者:getFileHost

  3.2.2 不配置file路由,请求‘http://127.0.0.1:8888/api/file/getFileHost?name=测试zuul’,返回404找不到路径,说明网关不能代理study-file的外露接口。

zuul:
  prefix: /api                                        # 配置请求路径前缀,所有基于此前缀的请求都由zuul网关提供代理
  routes:
    study-user-service.path: /user/**                  # 使用路径方式匹配路由规则
    study-user-service.serviceId: study-user           # 使用服务名称匹配

#    study-file-service.path: /file/**                  # 使用路径方式匹配路由规则
#    study-file-service.serviceId: study-file           # 使用服务名称匹配

  请求结果如下

  因此,配置为:http://ip:port/api/appservice/**的请求提供zuul网关代理,可以将要访问服务进行前缀分类

  3.3 测试网关的容错机制

  增加容错回调代码:

package com.study.zuul.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 如果需要在Zuul网关服务中增加容错处理fallback,需要实现接口ZuulFallbackProvider
 * spring-cloud框架,在Edgware版本(包括)之后,声明接口ZuulFallbackProvider过期失效,
 * 提供了新的ZuulFallbackProvider的子接口 - FallbackProvider
 * 在老版本中提供的ZuulFallbackProvider中,定义了两个方法。
 * - String getRoute()
 * 当前的fallback容错处理逻辑处理的是哪一个服务。可以使用通配符‘*’代表为全部的服务提供容错处理。
 * 如果只为某一个服务提供容错,返回对应服务的spring.application.name值。
 * - ClientHttpResponse fallbackResponse()
 * 当服务发生错误的时候,如何容错。
 * 新版本中提供的FallbackProvider提供了新的方法。
 * - ClientHttpResponse fallbackResponse(Throwable cause)
 * 如果使用新版本中定义的接口来做容错处理,容错处理逻辑,只运行子接口中定义的新方法。也就是有参方法。
 * 是为远程服务发生异常的时候,通过异常的类型来运行不同的容错逻辑。
 *
 * @author zhangpba
 * @date 2021-06-07
 */
@Component
public class TestFallBackProvider implements ZuulFallbackProvider {

    /**
     * 返回fallback处理哪一个服务。返回的是服务的名称
     * <p>
     * 推荐 - 为指定的服务定义特性化的fallback逻辑。
     * 推荐 - 提供一个处理所有服务的fallback逻辑。
     * 好处 - 服务某个服务发生超时,那么指定的fallback逻辑执行。如果有新服务上线,未提供fallback逻辑,有一个通用的。
     *
     * @return 返回的是服务的名称
     */
    @Override
    public String getRoute() {
        return "study-user";
    }

    /**
     * fallback逻辑。在早期版本中使用。(此版本就是早期版本)
     * Edgware版本之后,ZuulFallbackProvider接口过期,提供了新的子接口FallbackProvider
     * 子接口中提供了方法ClientHttpResponse fallbackResponse(Throwable cause)。
     * 优先调用子接口新定义的fallback处理逻辑。
     *
     * @return
     */
    @Override
    public ClientHttpResponse fallbackResponse() {
        System.out.println(" ClientHttpResponse fallbackResponse()");
        List<Map<String, Object>> result = new ArrayList<>();
        Map<String, Object> data = new HashMap<>();
        data.put("message", "服务器正忙,请稍后重试!");
        result.add(data);

        ObjectMapper mapper = new ObjectMapper();

        String msg = "";
        try {
            msg = mapper.writeValueAsString(result);
        } catch (JsonProcessingException e) {
            msg = "";
        }

        return this.executeFallback(HttpStatus.OK, msg, "application", "json", "utf-8");
    }

    /**
     * 具体处理过程。
     *
     * @param status       容错处理后的返回状态,如200正常GET请求结果,201正常POST请求结果,404资源找不到错误等。使用spring提供的枚举类型对象实现。HttpStatus
     * @param contentMsg   自定义的响应内容。就是反馈给客户端的数据。
     * @param mediaType    响应类型,是响应的主类型, 如: application、text、media。
     * @param subMediaType 响应类型,是响应的子类型, 如: json、stream、html、plain、jpeg、png等。
     * @param charsetName  响应结果的字符集。这里只传递字符集名称,如: utf-8、gbk、big5等。
     * @return ClientHttpResponse 就是响应的具体内容。
     * 相当于一个HttpServletResponse。
     */
    private final ClientHttpResponse executeFallback(final HttpStatus status, String
            contentMsg, String mediaType, String subMediaType, String charsetName) {
        return new ClientHttpResponse() {
            /**
             * 设置响应头信息
             *
             * @return
             */
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                MediaType mt = new MediaType(mediaType, subMediaType, Charset.forName(charsetName));
                headers.setContentType(mt);
                return headers;
            }

            /**
             * 设置响应体
             *
             * @return 响应体的流
             * @throws IOException
             */
            @Override
            public InputStream getBody() throws IOException {
                String content = contentMsg;
                return new ByteArrayInputStream(content.getBytes());
            }

            /**
             * 设置响应体的状态码
             *
             * @return 状态码 httpStatus
             * @throws IOException
             */
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return status;
            }

            /**
             * 设置响应体的状态码 int
             * @return int
             * @throws IOException
             */
            @Override
            public int getRawStatusCode() throws IOException {
                return 0;
            }

            /**
             * 设置响应体的状态码 String
             * @return
             * @throws IOException
             */
            @Override
            public String getStatusText() throws IOException {
                return this.getStatusCode().getReasonPhrase();
            }


            /**
             * 回收资源
             *
             * 用于回收当前fallback逻辑开启的资源对象的。
             * 不要关闭getBody方法返回的那个输入流对象。
             */
            @Override
            public void close() {

            }

        };
    }

    /**
     * fallback逻辑。优先调用。可以根据异常类型动态决定处理方式。
     */
//    @Override
//    public ClientHttpResponse fallbackResponse(Throwable cause) {
//        System.out.println("ClientHttpResponse fallbackResponse(Throwable cause)");
//        if(cause instanceof NullPointerException){
//
//            List<Map<String, Object>> result = new ArrayList<>();
//            Map<String, Object> data = new HashMap<>();
//            data.put("message", "网关超时,请稍后重试");
//            result.add(data);
//
//            ObjectMapper mapper = new ObjectMapper();
//
//            String msg = "";
//            try {
//                msg = mapper.writeValueAsString(result);
//            } catch (JsonProcessingException e) {
//                msg = "";
//            }
//
//            return this.executeFallback(HttpStatus.GATEWAY_TIMEOUT,
//                    msg, "application", "json", "utf-8");
//        }else{
//            return this.fallbackResponse();
//        }
//    }
}

  只开启study-eureka、study-zuul,关闭study-user,请求:http://localhost:8888/api/user/client/getFile?name=测试zuul,可以看到通过网关请求访问study-user时,请求超时,超时的提示信息是由我们刚写的容错代码body体返回来的

  返回结果:

  

 

posted @ 2021-06-02 23:37  zhangpba  阅读(144)  评论(0编辑  收藏  举报